diff options
author | Melanie Thielker | 2008-09-27 05:31:43 +0000 |
---|---|---|
committer | Melanie Thielker | 2008-09-27 05:31:43 +0000 |
commit | 85068dae60db02b168a29ffd75e1408e30d279e1 (patch) | |
tree | 8389c246a6e9891eb1bf310b85cba19a1668d790 /OpenSim/Region/ScriptEngine/Shared/CodeTools | |
parent | Mantis #2277 (diff) | |
download | opensim-SC_OLD-85068dae60db02b168a29ffd75e1408e30d279e1.zip opensim-SC_OLD-85068dae60db02b168a29ffd75e1408e30d279e1.tar.gz opensim-SC_OLD-85068dae60db02b168a29ffd75e1408e30d279e1.tar.bz2 opensim-SC_OLD-85068dae60db02b168a29ffd75e1408e30d279e1.tar.xz |
Add friendly error messages to both engines.
Diffstat (limited to 'OpenSim/Region/ScriptEngine/Shared/CodeTools')
-rw-r--r-- | OpenSim/Region/ScriptEngine/Shared/CodeTools/CSCodeGenerator.cs | 19 | ||||
-rw-r--r-- | OpenSim/Region/ScriptEngine/Shared/CodeTools/Compiler.cs | 225 |
2 files changed, 170 insertions, 74 deletions
diff --git a/OpenSim/Region/ScriptEngine/Shared/CodeTools/CSCodeGenerator.cs b/OpenSim/Region/ScriptEngine/Shared/CodeTools/CSCodeGenerator.cs index 86d6188..e8f2b71 100644 --- a/OpenSim/Region/ScriptEngine/Shared/CodeTools/CSCodeGenerator.cs +++ b/OpenSim/Region/ScriptEngine/Shared/CodeTools/CSCodeGenerator.cs | |||
@@ -89,7 +89,24 @@ namespace OpenSim.Region.ScriptEngine.Shared.CodeTools | |||
89 | ResetCounters(); | 89 | ResetCounters(); |
90 | Parser p = new LSLSyntax(new yyLSLSyntax(), new ErrorHandler(true)); | 90 | Parser p = new LSLSyntax(new yyLSLSyntax(), new ErrorHandler(true)); |
91 | // Obviously this needs to be in a try/except block. | 91 | // Obviously this needs to be in a try/except block. |
92 | LSL2CSCodeTransformer codeTransformer = new LSL2CSCodeTransformer(p.Parse(script)); | 92 | LSL2CSCodeTransformer codeTransformer; |
93 | try | ||
94 | { | ||
95 | codeTransformer = new LSL2CSCodeTransformer(p.Parse(script)); | ||
96 | } | ||
97 | catch (CSToolsException e) | ||
98 | { | ||
99 | string message; | ||
100 | |||
101 | // LL start numbering lines at 0 - geeks! | ||
102 | // | ||
103 | message = String.Format("Line ({0},{1}) {2}", | ||
104 | e.slInfo.lineNumber - 1, | ||
105 | e.slInfo.charPosition - 1, e.Message); | ||
106 | |||
107 | throw new Exception(message); | ||
108 | } | ||
109 | |||
93 | m_astRoot = codeTransformer.Transform(); | 110 | m_astRoot = codeTransformer.Transform(); |
94 | 111 | ||
95 | string retstr = String.Empty; | 112 | string retstr = String.Empty; |
diff --git a/OpenSim/Region/ScriptEngine/Shared/CodeTools/Compiler.cs b/OpenSim/Region/ScriptEngine/Shared/CodeTools/Compiler.cs index ec864e1..c6026fb 100644 --- a/OpenSim/Region/ScriptEngine/Shared/CodeTools/Compiler.cs +++ b/OpenSim/Region/ScriptEngine/Shared/CodeTools/Compiler.cs | |||
@@ -38,7 +38,7 @@ using OpenSim.Region.ScriptEngine.Interfaces; | |||
38 | 38 | ||
39 | namespace OpenSim.Region.ScriptEngine.Shared.CodeTools | 39 | namespace OpenSim.Region.ScriptEngine.Shared.CodeTools |
40 | { | 40 | { |
41 | public class Compiler | 41 | public class Compiler : ICompiler |
42 | { | 42 | { |
43 | // private static readonly log4net.ILog m_log | 43 | // private static readonly log4net.ILog m_log |
44 | // = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); | 44 | // = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); |
@@ -255,18 +255,14 @@ namespace OpenSim.Region.ScriptEngine.Shared.CodeTools | |||
255 | /// <returns>Filename to .dll assembly</returns> | 255 | /// <returns>Filename to .dll assembly</returns> |
256 | public string PerformScriptCompile(string Script, string asset) | 256 | public string PerformScriptCompile(string Script, string asset) |
257 | { | 257 | { |
258 | m_positionMap = null; | ||
259 | |||
258 | string OutFile = Path.Combine(ScriptEnginesPath, Path.Combine( | 260 | string OutFile = Path.Combine(ScriptEnginesPath, Path.Combine( |
259 | m_scriptEngine.World.RegionInfo.RegionID.ToString(), | 261 | m_scriptEngine.World.RegionInfo.RegionID.ToString(), |
260 | FilePrefix + "_compiled_" + asset + ".dll")); | 262 | FilePrefix + "_compiled_" + asset + ".dll")); |
261 | // string OutFile = Path.Combine(ScriptEnginesPath, | 263 | // string OutFile = Path.Combine(ScriptEnginesPath, |
262 | // FilePrefix + "_compiled_" + asset + ".dll"); | 264 | // FilePrefix + "_compiled_" + asset + ".dll"); |
263 | 265 | ||
264 | if (File.Exists(OutFile)) | ||
265 | { | ||
266 | m_scriptEngine.Log.DebugFormat("[Compiler] Returning existing assembly for {0}", asset); | ||
267 | return OutFile; | ||
268 | } | ||
269 | |||
270 | if (!Directory.Exists(ScriptEnginesPath)) | 266 | if (!Directory.Exists(ScriptEnginesPath)) |
271 | { | 267 | { |
272 | try | 268 | try |
@@ -327,38 +323,26 @@ namespace OpenSim.Region.ScriptEngine.Shared.CodeTools | |||
327 | compileScript = LSL_Converter.Convert(Script); | 323 | compileScript = LSL_Converter.Convert(Script); |
328 | 324 | ||
329 | m_positionMap = ((CSCodeGenerator) LSL_Converter).PositionMap; | 325 | m_positionMap = ((CSCodeGenerator) LSL_Converter).PositionMap; |
326 | } | ||
330 | 327 | ||
331 | l = enumCompileType.cs; | 328 | // Check this late so the map is generated on sim start |
329 | // | ||
330 | if (File.Exists(OutFile)) | ||
331 | { | ||
332 | m_scriptEngine.Log.DebugFormat("[Compiler] Returning existing assembly for {0}", asset); | ||
333 | return OutFile; | ||
332 | } | 334 | } |
333 | 335 | ||
334 | if (l == enumCompileType.yp) | 336 | if (l == enumCompileType.yp) |
335 | { | 337 | { |
336 | // Its YP, convert it to C# | 338 | // Its YP, convert it to C# |
337 | compileScript = YP_Converter.Convert(Script); | 339 | compileScript = YP_Converter.Convert(Script); |
338 | // We have our own processor now | ||
339 | //l = enumCompileType.cs; | ||
340 | } | ||
341 | |||
342 | // Insert additional assemblies here | ||
343 | |||
344 | //ADAM: Disabled for the moment until it's working right. | ||
345 | bool enableCommanderLSL = false; | ||
346 | |||
347 | if (enableCommanderLSL == true && ((l == enumCompileType.cs) || (l == enumCompileType.yp))) | ||
348 | { | ||
349 | foreach (KeyValuePair<string, | ||
350 | ICommander> com | ||
351 | in m_scriptEngine.World.GetCommanders()) | ||
352 | { | ||
353 | compileScript = com.Value.GenerateRuntimeAPI() + compileScript; | ||
354 | } | ||
355 | } | 340 | } |
356 | 341 | ||
357 | // End of insert | ||
358 | |||
359 | switch (l) | 342 | switch (l) |
360 | { | 343 | { |
361 | case enumCompileType.cs: | 344 | case enumCompileType.cs: |
345 | case enumCompileType.lsl: | ||
362 | compileScript = CreateCSCompilerScript(compileScript); | 346 | compileScript = CreateCSCompilerScript(compileScript); |
363 | break; | 347 | break; |
364 | case enumCompileType.vb: | 348 | case enumCompileType.vb: |
@@ -372,10 +356,6 @@ namespace OpenSim.Region.ScriptEngine.Shared.CodeTools | |||
372 | break; | 356 | break; |
373 | } | 357 | } |
374 | 358 | ||
375 | // m_log.Debug("[ScriptEngine.DotNetEngine]: Preparing to compile the following LSL to C# translated code"); | ||
376 | // m_log.Debug(""); | ||
377 | // m_log.Debug(compileScript); | ||
378 | |||
379 | return CompileFromDotNetText(compileScript, l, asset); | 359 | return CompileFromDotNetText(compileScript, l, asset); |
380 | } | 360 | } |
381 | 361 | ||
@@ -442,26 +422,24 @@ namespace OpenSim.Region.ScriptEngine.Shared.CodeTools | |||
442 | // Output assembly name | 422 | // Output assembly name |
443 | scriptCompileCounter++; | 423 | scriptCompileCounter++; |
444 | string OutFile = Path.Combine(ScriptEnginesPath, Path.Combine( | 424 | string OutFile = Path.Combine(ScriptEnginesPath, Path.Combine( |
445 | m_scriptEngine.World.RegionInfo.RegionID.ToString(), | 425 | m_scriptEngine.World.RegionInfo.RegionID.ToString(), |
446 | FilePrefix + "_compiled_" + asset + ".dll")); | 426 | FilePrefix + "_compiled_" + asset + ".dll")); |
447 | #if DEBUG | ||
448 | // m_scriptEngine.Log.Debug("[Compiler]: Starting compile of \"" + OutFile + "\"."); | ||
449 | #endif | ||
450 | try | 427 | try |
451 | { | 428 | { |
452 | File.Delete(OutFile); | 429 | File.Delete(OutFile); |
453 | } | 430 | } |
454 | catch (Exception e) // NOTLEGIT - Should be just catching FileIOException | 431 | catch (Exception e) // NOTLEGIT - Should be just FileIOException |
455 | { | 432 | { |
456 | //m_scriptEngine.Log.Error("[Compiler]: Unable to delete old existring script-file before writing new. Compile aborted: " + e.ToString()); | 433 | throw new Exception("Unable to delete old existing "+ |
457 | throw new Exception("Unable to delete old existring script-file before writing new. Compile aborted: " + e.ToString()); | 434 | "script-file before writing new. Compile aborted: " + |
435 | e.ToString()); | ||
458 | } | 436 | } |
459 | //string OutFile = Path.Combine("ScriptEngines", "SecondLife.Script.dll"); | ||
460 | 437 | ||
461 | // DEBUG - write source to disk | 438 | // DEBUG - write source to disk |
462 | if (WriteScriptSourceToDebugFile) | 439 | if (WriteScriptSourceToDebugFile) |
463 | { | 440 | { |
464 | string srcFileName = FilePrefix + "_source_" + Path.GetFileNameWithoutExtension(OutFile) + ext; | 441 | string srcFileName = FilePrefix + "_source_" + |
442 | Path.GetFileNameWithoutExtension(OutFile) + ext; | ||
465 | try | 443 | try |
466 | { | 444 | { |
467 | File.WriteAllText(Path.Combine(Path.Combine( | 445 | File.WriteAllText(Path.Combine(Path.Combine( |
@@ -469,9 +447,11 @@ namespace OpenSim.Region.ScriptEngine.Shared.CodeTools | |||
469 | m_scriptEngine.World.RegionInfo.RegionID.ToString()), | 447 | m_scriptEngine.World.RegionInfo.RegionID.ToString()), |
470 | srcFileName), Script); | 448 | srcFileName), Script); |
471 | } | 449 | } |
472 | catch (Exception ex) // NOTLEGIT - Should be just catching FileIOException | 450 | catch (Exception ex) //NOTLEGIT - Should be just FileIOException |
473 | { | 451 | { |
474 | m_scriptEngine.Log.Error("[Compiler]: Exception while trying to write script source to file \"" + srcFileName + "\": " + ex.ToString()); | 452 | m_scriptEngine.Log.Error("[Compiler]: Exception while "+ |
453 | "trying to write script source to file \"" + | ||
454 | srcFileName + "\": " + ex.ToString()); | ||
475 | } | 455 | } |
476 | } | 456 | } |
477 | 457 | ||
@@ -480,22 +460,18 @@ namespace OpenSim.Region.ScriptEngine.Shared.CodeTools | |||
480 | 460 | ||
481 | parameters.IncludeDebugInformation = true; | 461 | parameters.IncludeDebugInformation = true; |
482 | 462 | ||
483 | // Add all available assemblies | 463 | string rootPath = |
484 | // foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) | 464 | Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory); |
485 | // { | ||
486 | // Console.WriteLine("Adding assembly: " + asm.Location); | ||
487 | // parameters.ReferencedAssemblies.Add(asm.Location); | ||
488 | // } | ||
489 | 465 | ||
490 | string rootPath = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory); | 466 | parameters.ReferencedAssemblies.Add(Path.Combine(rootPath, |
491 | // string rootPathSE = Path.GetDirectoryName(GetType().Assembly.Location); | 467 | "OpenSim.Region.ScriptEngine.Shared.dll")); |
492 | //Console.WriteLine("Assembly location: " + rootPath); | 468 | parameters.ReferencedAssemblies.Add(Path.Combine(rootPath, |
493 | parameters.ReferencedAssemblies.Add(Path.Combine(rootPath, "OpenSim.Region.ScriptEngine.Shared.dll")); | 469 | "OpenSim.Region.ScriptEngine.Shared.Api.Runtime.dll")); |
494 | parameters.ReferencedAssemblies.Add(Path.Combine(rootPath, "OpenSim.Region.ScriptEngine.Shared.Api.Runtime.dll")); | ||
495 | 470 | ||
496 | if (lang == enumCompileType.yp) | 471 | if (lang == enumCompileType.yp) |
497 | { | 472 | { |
498 | parameters.ReferencedAssemblies.Add(Path.Combine(rootPath, "OpenSim.Region.ScriptEngine.Shared.YieldProlog.dll")); | 473 | parameters.ReferencedAssemblies.Add(Path.Combine(rootPath, |
474 | "OpenSim.Region.ScriptEngine.Shared.YieldProlog.dll")); | ||
499 | } | 475 | } |
500 | 476 | ||
501 | parameters.GenerateExecutable = false; | 477 | parameters.GenerateExecutable = false; |
@@ -504,24 +480,29 @@ namespace OpenSim.Region.ScriptEngine.Shared.CodeTools | |||
504 | //parameters.WarningLevel = 1; // Should be 4? | 480 | //parameters.WarningLevel = 1; // Should be 4? |
505 | parameters.TreatWarningsAsErrors = false; | 481 | parameters.TreatWarningsAsErrors = false; |
506 | 482 | ||
507 | //Console.WriteLine(Script); | ||
508 | CompilerResults results; | 483 | CompilerResults results; |
509 | switch (lang) | 484 | switch (lang) |
510 | { | 485 | { |
511 | case enumCompileType.vb: | 486 | case enumCompileType.vb: |
512 | results = VBcodeProvider.CompileAssemblyFromSource(parameters, Script); | 487 | results = VBcodeProvider.CompileAssemblyFromSource( |
488 | parameters, Script); | ||
513 | break; | 489 | break; |
514 | case enumCompileType.cs: | 490 | case enumCompileType.cs: |
515 | results = CScodeProvider.CompileAssemblyFromSource(parameters, Script); | 491 | case enumCompileType.lsl: |
492 | results = CScodeProvider.CompileAssemblyFromSource( | ||
493 | parameters, Script); | ||
516 | break; | 494 | break; |
517 | case enumCompileType.js: | 495 | case enumCompileType.js: |
518 | results = JScodeProvider.CompileAssemblyFromSource(parameters, Script); | 496 | results = JScodeProvider.CompileAssemblyFromSource( |
497 | parameters, Script); | ||
519 | break; | 498 | break; |
520 | case enumCompileType.yp: | 499 | case enumCompileType.yp: |
521 | results = YPcodeProvider.CompileAssemblyFromSource(parameters, Script); | 500 | results = YPcodeProvider.CompileAssemblyFromSource( |
501 | parameters, Script); | ||
522 | break; | 502 | break; |
523 | default: | 503 | default: |
524 | throw new Exception("Compiler is not able to recongnize language type \"" + lang.ToString() + "\""); | 504 | throw new Exception("Compiler is not able to recongnize "+ |
505 | "language type \"" + lang.ToString() + "\""); | ||
525 | } | 506 | } |
526 | 507 | ||
527 | // Check result | 508 | // Check result |
@@ -530,11 +511,18 @@ namespace OpenSim.Region.ScriptEngine.Shared.CodeTools | |||
530 | // | 511 | // |
531 | // WARNINGS AND ERRORS | 512 | // WARNINGS AND ERRORS |
532 | // | 513 | // |
514 | int display = 5; | ||
533 | if (results.Errors.Count > 0) | 515 | if (results.Errors.Count > 0) |
534 | { | 516 | { |
535 | string errtext = String.Empty; | 517 | string errtext = String.Empty; |
536 | foreach (CompilerError CompErr in results.Errors) | 518 | foreach (CompilerError CompErr in results.Errors) |
537 | { | 519 | { |
520 | // Show 5 errors max | ||
521 | // | ||
522 | if (display <= 0) | ||
523 | break; | ||
524 | display--; | ||
525 | |||
538 | string severity = "Error"; | 526 | string severity = "Error"; |
539 | if ( CompErr.IsWarning ) | 527 | if ( CompErr.IsWarning ) |
540 | { | 528 | { |
@@ -543,22 +531,21 @@ namespace OpenSim.Region.ScriptEngine.Shared.CodeTools | |||
543 | 531 | ||
544 | KeyValuePair<int, int> lslPos; | 532 | KeyValuePair<int, int> lslPos; |
545 | 533 | ||
546 | try | 534 | lslPos = FindErrorPosition(CompErr.Line, CompErr.Column); |
547 | { | 535 | |
548 | lslPos = m_positionMap[new KeyValuePair<int, int>(CompErr.Line, CompErr.Column)]; | 536 | string text = CompErr.ErrorText; |
549 | } | 537 | |
550 | catch (KeyNotFoundException) // we don't have this line/column mapped | 538 | // Use LSL type names |
551 | { | 539 | if (lang == enumCompileType.lsl) |
552 | m_scriptEngine.Log.Debug(String.Format("[Compiler]: Lookup of C# line {0}, column {1} failed.", CompErr.Line, CompErr.Column)); | 540 | text = ReplaceTypes(CompErr.ErrorText); |
553 | lslPos = new KeyValuePair<int, int>(-CompErr.Line, -CompErr.Column); | ||
554 | } | ||
555 | 541 | ||
556 | // The Second Life viewer's script editor begins | 542 | // The Second Life viewer's script editor begins |
557 | // countingn lines and columns at 0, so we subtract 1. | 543 | // countingn lines and columns at 0, so we subtract 1. |
558 | errtext += String.Format("Line {0}, column {1}, {4} Number: {2}, '{3}'\r\n", lslPos.Key - 1, lslPos.Value - 1, CompErr.ErrorNumber, CompErr.ErrorText, severity); | 544 | errtext += String.Format("Line ({0},{1}): {4} {2}: {3}\n", |
545 | lslPos.Key - 1, lslPos.Value - 1, | ||
546 | CompErr.ErrorNumber, text, severity); | ||
559 | } | 547 | } |
560 | 548 | ||
561 | Console.WriteLine("[COMPILER MESSAGES]: " + errtext); | ||
562 | if (!File.Exists(OutFile)) | 549 | if (!File.Exists(OutFile)) |
563 | { | 550 | { |
564 | throw new Exception(errtext); | 551 | throw new Exception(errtext); |
@@ -574,8 +561,100 @@ namespace OpenSim.Region.ScriptEngine.Shared.CodeTools | |||
574 | errtext += "No compile error. But not able to locate compiled file."; | 561 | errtext += "No compile error. But not able to locate compiled file."; |
575 | throw new Exception(errtext); | 562 | throw new Exception(errtext); |
576 | } | 563 | } |
577 | m_scriptEngine.Log.DebugFormat("[Compiler] Compiled new assembly for {0}", asset); | 564 | m_scriptEngine.Log.DebugFormat("[Compiler] Compiled new assembly "+ |
565 | "for {0}", asset); | ||
578 | return OutFile; | 566 | return OutFile; |
579 | } | 567 | } |
568 | |||
569 | public KeyValuePair<int, int> FindErrorPosition(int line, int col) | ||
570 | { | ||
571 | return FindErrorPosition(line, col, m_positionMap); | ||
572 | } | ||
573 | |||
574 | private class kvpSorter : IComparer<KeyValuePair<int,int>> | ||
575 | { | ||
576 | public int Compare(KeyValuePair<int,int> a, | ||
577 | KeyValuePair<int,int> b) | ||
578 | { | ||
579 | return a.Key.CompareTo(b.Key); | ||
580 | } | ||
581 | } | ||
582 | |||
583 | public static KeyValuePair<int, int> FindErrorPosition(int line, | ||
584 | int col, Dictionary<KeyValuePair<int, int>, | ||
585 | KeyValuePair<int, int>> positionMap) | ||
586 | { | ||
587 | if (positionMap == null || positionMap.Count == 0) | ||
588 | return new KeyValuePair<int, int>(line, col); | ||
589 | |||
590 | KeyValuePair<int, int> ret = new KeyValuePair<int, int>(); | ||
591 | |||
592 | if (positionMap.TryGetValue(new KeyValuePair<int, int>(line, col), | ||
593 | out ret)) | ||
594 | return ret; | ||
595 | |||
596 | List<KeyValuePair<int,int>> sorted = | ||
597 | new List<KeyValuePair<int,int>>(positionMap.Keys); | ||
598 | |||
599 | sorted.Sort(new kvpSorter()); | ||
600 | |||
601 | int l = 1; | ||
602 | int c = 1; | ||
603 | |||
604 | foreach (KeyValuePair<int, int> cspos in sorted) | ||
605 | { | ||
606 | if (cspos.Key >= line) | ||
607 | { | ||
608 | if (cspos.Key > line) | ||
609 | return new KeyValuePair<int, int>(l, c); | ||
610 | if (cspos.Value > col) | ||
611 | return new KeyValuePair<int, int>(l, c); | ||
612 | c = cspos.Value; | ||
613 | if (c == 0) | ||
614 | c++; | ||
615 | } | ||
616 | else | ||
617 | { | ||
618 | l = cspos.Key; | ||
619 | } | ||
620 | } | ||
621 | return new KeyValuePair<int, int>(l, c); | ||
622 | } | ||
623 | |||
624 | string ReplaceTypes(string message) | ||
625 | { | ||
626 | message = message.Replace( | ||
627 | "OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString", | ||
628 | "string"); | ||
629 | |||
630 | message = message.Replace( | ||
631 | "OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger", | ||
632 | "integer"); | ||
633 | |||
634 | message = message.Replace( | ||
635 | "OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat", | ||
636 | "float"); | ||
637 | |||
638 | message = message.Replace( | ||
639 | "OpenSim.Region.ScriptEngine.Shared.LSL_Types.list", | ||
640 | "list"); | ||
641 | |||
642 | return message; | ||
643 | } | ||
644 | |||
645 | public Dictionary<KeyValuePair<int, int>, KeyValuePair<int, int>> | ||
646 | LineMap() | ||
647 | { | ||
648 | if (m_positionMap == null) | ||
649 | return null; | ||
650 | |||
651 | Dictionary<KeyValuePair<int, int>, KeyValuePair<int, int>> ret = | ||
652 | new Dictionary<KeyValuePair<int,int>, KeyValuePair<int, int>>(); | ||
653 | |||
654 | foreach (KeyValuePair<int, int> kvp in m_positionMap.Keys) | ||
655 | ret.Add(kvp, m_positionMap[kvp]); | ||
656 | |||
657 | return ret; | ||
658 | } | ||
580 | } | 659 | } |
581 | } | 660 | } |