From 5f7d8503456ab0bcd469545fc569699657cdb60e Mon Sep 17 00:00:00 2001 From: genywind Date: Tue, 25 Oct 2011 18:28:25 +0400 Subject: [PATCH] wp7: update IronRuby, fix issue with RubyExceptions --- lib/framework/rho/rho.rb | 2 +- .../Ruby/Libraries/Builtins/RangeOps.cs | 589 +++++++++++++++ .../Ruby/Ruby/Builtins/RubyEncoding.cs | 678 ++++++++++++++++++ .../Ruby/Ruby/Runtime/RubyExceptionData.cs | 271 +++++++ .../Languages/Ruby/Ruby/Runtime/RubyUtils.cs | 32 +- 5 files changed, 1570 insertions(+), 2 deletions(-) create mode 100644 platform/wp7/IronRuby/Languages/Ruby/Libraries/Builtins/RangeOps.cs create mode 100644 platform/wp7/IronRuby/Languages/Ruby/Ruby/Builtins/RubyEncoding.cs create mode 100644 platform/wp7/IronRuby/Languages/Ruby/Ruby/Runtime/RubyExceptionData.cs diff --git a/lib/framework/rho/rho.rb b/lib/framework/rho/rho.rb index d619f3a15b2..33c2f13ff4d 100644 --- a/lib/framework/rho/rho.rb +++ b/lib/framework/rho/rho.rb @@ -957,7 +957,7 @@ def self.current_exception def send_error(exception=nil,status=500,hash=false) if exception trace_msg = exception.backtrace.join("\n") - puts "App error: #{exception}\n #{trace_msg}" + puts "App error: #{exception.message}\n #{trace_msg}" end body='' diff --git a/platform/wp7/IronRuby/Languages/Ruby/Libraries/Builtins/RangeOps.cs b/platform/wp7/IronRuby/Languages/Ruby/Libraries/Builtins/RangeOps.cs new file mode 100644 index 00000000000..61442772211 --- /dev/null +++ b/platform/wp7/IronRuby/Languages/Ruby/Libraries/Builtins/RangeOps.cs @@ -0,0 +1,589 @@ +/* **************************************************************************** + * + * Copyright (c) Microsoft Corporation. + * + * This source code is subject to terms and conditions of the Apache License, Version 2.0. A + * copy of the license can be found in the License.html file at the root of this distribution. If + * you cannot locate the Apache License, Version 2.0, please send an email to + * ironruby@microsoft.com. By using this source code in any fashion, you are agreeing to be bound + * by the terms of the Apache License, Version 2.0. + * + * You must not remove this notice, or any other, from this software. + * + * + * ***************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Reflection; +using Microsoft.Scripting.Runtime; +using Microsoft.Scripting.Actions; +using Microsoft.Scripting.Generation; +using Microsoft.Scripting.Utils; +using IronRuby.Compiler.Generation; +using IronRuby.Runtime; +using IronRuby.Runtime.Calls; +using IronRuby.Runtime.Conversions; +using System.Linq; + +namespace IronRuby.Builtins { + using UnaryOp = Func; + using BinaryOp = Func; + + /// + /// A Range represents an interval—a set of values with a start and an end. + /// Ranges may be constructed using the s..e and s…e literals, or with Range::new. + /// Ranges constructed using .. run from the start to the end inclusively. + /// Those created using … exclude the end value. + /// When used as an iterator, ranges return each value in the sequence. + /// + /// + /// (-1..-5).to_a #=> [] + /// (-5..-1).to_a #=> [-5, -4, -3, -2, -1] + /// ('a'..'e').to_a #=> ["a", "b", "c", "d", "e"] + /// ('a'...'e').to_a #=> ["a", "b", "c", "d"] + /// + /// + /// Ranges can be constructed using objects of any type, as long as the objects can be compared using their <=> operator + /// and they support the succ method to return the next object in sequence. + /// + [RubyClass("Range", Extends = typeof(Range), Inherits = typeof(Object)), Includes(typeof(Enumerable))] + public static class RangeOps { + + #region Construction and Initialization + + /// + /// Construct a new Range object. + /// + /// + /// An empty Range object + /// + /// + /// This constructor only creates an empty range object, + /// which will be initialized subsequently by a separate call through into one of the two initialize methods. + /// Literal Ranges (e.g. 1..5, 'a'...'b' are created by calls through to RubyOps.CreateInclusiveRange and + /// RubyOps.CreateExclusiveRange which bypass this constructor/initializer run about and initialize the object directly. + /// + [RubyConstructor] + public static Range/*!*/ CreateRange(BinaryOpStorage/*!*/ comparisonStorage, + RubyClass/*!*/ self, object begin, object end, [Optional]bool excludeEnd) { + return new Range(comparisonStorage, self.Context, begin, end, excludeEnd); + } + + // Reinitialization. Not called when a factory/non-default ctor is called. + [RubyMethod("initialize", RubyMethodAttributes.PrivateInstance)] + public static Range/*!*/ Reinitialize(BinaryOpStorage/*!*/ comparisonStorage, + RubyContext/*!*/ context, Range/*!*/ self, object begin, object end, [Optional]bool excludeEnd) { + self.Initialize(comparisonStorage, context, begin, end, excludeEnd); + return self; + } + + #endregion + + #region begin, first, end, last, exclude_end? + + /// + /// Returns the first object in self + /// + [RubyMethod("begin"), RubyMethod("first")] + public static object Begin([NotNull]Range/*!*/ self) { + return self.Begin; + } + + /// + /// Returns the object that defines the end of self + /// + [RubyMethod("end"), RubyMethod("last")] + public static object End([NotNull]Range/*!*/ self) { + return self.End; + } + + /// + /// Returns true if self excludes its end value. + /// + [RubyMethod("exclude_end?")] + public static bool ExcludeEnd([NotNull]Range/*!*/ self) { + return self.ExcludeEnd; + } + + #endregion + + #region inspect, to_s + + /// + /// Convert this range object to a printable form (using inspect to convert the start and end objects). + /// + [RubyMethod("inspect")] + public static MutableString/*!*/ Inspect(RubyContext/*!*/ context, Range/*!*/ self) { + return self.Inspect(context); + } + + /// + /// Convert this range object to a printable form (using to_s to convert the start and end objects). + /// + [RubyMethod("to_s")] + public static MutableString/*!*/ ToS(ConversionStorage/*!*/ tosConversion, Range/*!*/ self) { + return self.ToMutableString(tosConversion); + } + + #endregion + + #region ==, eql? + + /// + /// Is other equal to self? Here other is not a Range so returns false. + /// + [RubyMethod("=="), RubyMethod("eql?")] + public static bool Equals(Range/*!*/ self, object other) { + return false; + } + + /// + /// Returns true only if self is a Range, has equivalent beginning and end items (by comparing them with ==), + /// and has the same exclude_end? setting as other. + /// + /// + /// (0..2) == (0..2) #=> true + /// (0..2) == Range.new(0,2) #=> true + /// (0..2) == (0...2) #=> false + /// (0..2).eql?(0.0..2.0) #=> true + /// + [RubyMethod("==")] + public static bool Equals(BinaryOpStorage/*!*/ equals, Range/*!*/ self, [NotNull]Range/*!*/ other) { + if (self == other) { + return true; + } + return Protocols.IsEqual(equals, self.Begin, other.Begin) + && Protocols.IsEqual(equals, self.End, other.End) + && self.ExcludeEnd == other.ExcludeEnd; + } + + /// + /// Returns true only if self is a Range, has equivalent beginning and end items (by comparing them with eql?), + /// and has the same exclude_end? setting as other. + /// + /// + /// (0..2).eql?(0..2) #=> true + /// (0..2).eql?(Range.new(0,2)) #=> true + /// (0..2).eql?(0...2) #=> false + /// (0..2).eql?(0.0..2.0) #=> false + /// + [RubyMethod("eql?")] + public static bool Eql(BinaryOpStorage/*!*/ equalsStorage, Range/*!*/ self, [NotNull]Range/*!*/ other) { + if (self == other) { + return true; + } + + var site = equalsStorage.GetCallSite("eql?"); + return Protocols.IsTrue(site.Target(site, self.Begin, other.Begin)) + && Protocols.IsTrue(site.Target(site, self.End, other.End)) + && self.ExcludeEnd == other.ExcludeEnd; + } + + #endregion + + #region ===, member?, include? + + /// + /// Returns true if other is an element of self, false otherwise. + /// Conveniently, === is the comparison operator used by case statements. + /// + /// + /// case 79 + /// when 1..50 then print "low\n" + /// when 51..75 then print "medium\n" + /// when 76..100 then print "high\n" + /// end + /// => "high" + /// + [RubyMethod("==="), RubyMethod("member?"), RubyMethod("include?")] + public static bool CaseEquals(ComparisonStorage/*!*/ comparisonStorage, [NotNull]Range/*!*/ self, object value) { + var compare = comparisonStorage.CompareSite; + + object result = compare.Target(compare, self.Begin, value); + if (result == null || Protocols.ConvertCompareResult(comparisonStorage, result) > 0) { + return false; + } + + result = compare.Target(compare, value, self.End); + if (result == null) { + return false; + } + + int valueToEnd = Protocols.ConvertCompareResult(comparisonStorage, result); + return valueToEnd < 0 || (!self.ExcludeEnd && valueToEnd == 0); + } + + #endregion + + #region hash + + /// + /// Generate a hash value such that two ranges with the same start and end points, + /// and the same value for the "exclude end" flag, generate the same hash value. + /// + [RubyMethod("hash")] + public static int GetHashCode(UnaryOpStorage/*!*/ hashStorage, Range/*!*/ self) { + // MRI: Ruby treatment of hash return value is inconsistent. + // No conversions happen here (unlike e.g. Array.hash). + var hashSite = hashStorage.GetCallSite("hash"); + return unchecked( + Protocols.ToHashCode(hashSite.Target(hashSite, self.Begin)) ^ + Protocols.ToHashCode(hashSite.Target(hashSite, self.End)) ^ + (self.ExcludeEnd ? 179425693 : 1794210891) + ); + } + + #endregion + + #region each, step + + public class EachStorage : ComparisonStorage { + private CallSite _equalsSite; // == + private CallSite _succSite; // succ + private CallSite _respondToSite; // respond_to? + private CallSite> _fixnumCast; + private CallSite _addSite; // + + + [Emitted] + public EachStorage(RubyContext/*!*/ context) : base(context) { } + + public CallSite/*!*/ RespondToSite { + get { return RubyUtils.GetCallSite(ref _respondToSite, Context, "respond_to?", 1); } + } + + public CallSite/*!*/ EqualsSite { + get { return RubyUtils.GetCallSite(ref _equalsSite, Context, "==", 1); } + } + + public CallSite/*!*/ SuccSite { + get { return RubyUtils.GetCallSite(ref _succSite, Context, "succ", 0); } + } + + public CallSite>/*!*/ FixnumCastSite { + get { return RubyUtils.GetCallSite(ref _fixnumCast, ConvertToFixnumAction.Make(Context)); } + } + + public CallSite/*!*/ AddSite { + get { return RubyUtils.GetCallSite(ref _addSite, Context, "+", 1); } + } + } + + [RubyMethod("each")] + public static Enumerator/*!*/ GetEachEnumerator(EachStorage/*!*/ storage, Range/*!*/ self) { + return new Enumerator((_, block) => Each(storage, block, self)); + } + + /// + /// Iterates over the elements of self, passing each in turn to the block. + /// You can only iterate if the start object of the range supports the succ method + /// (which means that you can‘t iterate over ranges of Float objects). + /// + [RubyMethod("each")] + public static object Each(EachStorage/*!*/ storage, [NotNull]BlockParam/*!*/ block, Range/*!*/ self) { + Assert.NotNull(block); + CheckBegin(storage, self.Begin); + + var each = EachInRange(storage, self, 1); + + foreach (var item in each) { + object result; + if (block.Yield(item, out result)) { + return result; + } + } + + return self; + } + + public static IEnumerable EachInRange(EachStorage/*!*/ storage, Range/*!*/ self, object step) { + if (self.Begin is int && self.End is int) { + // self.begin is Fixnum; directly call item = item + 1 instead of succ + return EachStepFixnum(self, ConvertStepToInt(storage, step)); + } else if (self.Begin is MutableString) { + // self.begin is String; use item.succ and item <=> self.end but make sure you check the length of the strings + return (IEnumerable)EachStepString(storage, self, ConvertStepToInt(storage, step)); + } else if (storage.Context.IsInstanceOf(self.Begin, storage.Context.GetClass(typeof(Numeric)))) { + // self.begin is Numeric; invoke item = item + 1 instead of succ and invoke < or <= for compare + return EachStepNumeric(storage, self, step); + } else { + // self.begin is not Numeric or String; just invoke item.succ and item <=> self.end + return EachStepObject(storage, self, ConvertStepToInt(storage, step)); + } + } + + private static int ConvertStepToInt(EachStorage storage, object step) { + if (step is int) { + return (int)step; + } + + var site = storage.FixnumCastSite; + return site.Target(site, step); + } + + [RubyMethod("step")] + public static Enumerator/*!*/ GetStepEnumerator(EachStorage/*!*/ storage, Range/*!*/ self, [Optional]object step) { + return new Enumerator((_, block) => Step(storage, block, self, step)); + } + + /// + /// Iterates over self, passing each stepth element to the block. + /// If the range contains numbers or strings, natural ordering is used. + /// Otherwise step invokes succ to iterate through range elements. + /// + [RubyMethod("step")] + public static object Step(EachStorage/*!*/ storage, [NotNull]BlockParam/*!*/ block, Range/*!*/ self, [Optional]object step) { + if (step == Missing.Value) { + step = ClrInteger.One; + } + + Assert.NotNull(block); + + var each = EachInRange(storage, self, step); + + foreach (var item in each) { + object result; + if (block.Yield(item, out result)) { + return result; + } + } + + return self; + } + + /// + /// Step through a Range of Fixnums. + /// + /// + /// This method is optimized for direct integer operations using < and + directly. + /// It is not used if either begin or end are outside Fixnum bounds. + /// + private static IEnumerable EachStepFixnum(Range/*!*/ self, int step) { + Assert.NotNull(self); + CheckStep(step); + + int end = (int)self.End; + int item = (int)self.Begin; + + while (item < end) { + yield return item; + item += step; + } + + if (item == end && !self.ExcludeEnd) { + yield return item; + } + } + + /// + /// Step through a Range of Strings. + /// + /// + /// This method requires step to be a Fixnum. + /// It uses a hybrid string comparison to prevent infinite loops and calls String#succ to get each item in the range. + /// + private static IEnumerable EachStepString(ComparisonStorage/*!*/ storage, Range/*!*/ self, int step) { + Assert.NotNull(storage, self); + CheckStep(step); + + var begin = (MutableString)self.Begin; + var end = (MutableString)self.End; + + MutableString item = begin; + int comp; + + while ((comp = Protocols.Compare(storage, item, end)) < 0) { + yield return item.Clone(); + + if (ReferenceEquals(item, begin)) { + item = item.Clone(); + } + + // TODO: this can be optimized + for (int i = 0; i < step; i++) { + MutableStringOps.SuccInPlace(item); + } + + if (item.Length > end.Length) { + yield break; + } + } + + if (comp == 0 && !self.ExcludeEnd) { + yield return item.Clone(); + } + } + + /// + /// Step through a Range of Numerics. + /// + private static IEnumerable EachStepNumeric(EachStorage/*!*/ storage, Range/*!*/ self, object step) { + Assert.NotNull(storage, self); + CheckStep(storage, step); + + object item = self.Begin; + object end = self.End; + + int comp; + while ((comp = Protocols.Compare(storage, item, end)) < 0) { + yield return item; + + var add = storage.AddSite; + item = add.Target(add, item, step); + } + + if (comp == 0 && !self.ExcludeEnd) { + yield return item; + } + } + + /// + /// Step through a Range of objects that are not Numeric or String. + /// + private static IEnumerable EachStepObject(EachStorage/*!*/ storage, Range/*!*/ self, int step) { + Assert.NotNull(storage, self); + CheckStep(storage, step); + + int comp; + object begin = self.Begin; + + CheckBegin(storage, begin); + + object end = self.End; + object item = begin; + var succSite = storage.SuccSite; + + while ((comp = Protocols.Compare(storage, item, end)) < 0) { + yield return item; + + for (int i = 0; i < step; ++i) { + item = succSite.Target(succSite, item); + } + } + + if (comp == 0 && !self.ExcludeEnd) { + yield return item; + } + } + + /// + /// Check that the int is not less than or equal to zero. + /// + private static void CheckStep(int step) { + if (step == 0) { + throw RubyExceptions.CreateArgumentError("step can't be 0"); + } + if (step < 0) { + throw RubyExceptions.CreateArgumentError("step can't be negative"); + } + } + + /// + /// Check that the object, when converted to an integer, is not less than or equal to zero. + /// + private static void CheckStep(EachStorage/*!*/ storage, object step) { + var equals = storage.EqualsSite; + if (Protocols.IsTrue(equals.Target(equals, step, 0))) { + throw RubyExceptions.CreateArgumentError("step can't be 0"); + } + + var lessThan = storage.LessThanSite; + if (RubyOps.IsTrue(lessThan.Target(lessThan, step, 0))) { + throw RubyExceptions.CreateArgumentError("step can't be negative"); + } + } + + /// + /// Check that the object responds to "succ". + /// + private static void CheckBegin(EachStorage/*!*/ storage, object begin) { + if (!Protocols.RespondTo(storage.RespondToSite, storage.Context, begin, "succ")) { + throw RubyExceptions.CreateTypeError("can't iterate from {0}", storage.Context.GetClassDisplayName(begin)); + } + } + + #endregion + + #region cover?, min, max + + [RubyMethod("cover?")] + public static bool Cover(ComparisonStorage/*!*/ comparisonStorage, [NotNull]Range/*!*/ self, object value) { + return CaseEquals(comparisonStorage, self, value); + } + + private static bool IsBeginGreatherThanEnd(ComparisonStorage comparisonStorage, Range self) { + var compare = comparisonStorage.CompareSite; + + object result = compare.Target(compare, self.Begin, self.End); + + return result == null || + Protocols.ConvertCompareResult(comparisonStorage, result) > (self.ExcludeEnd ? -1 : 0); + } + + [RubyMethod("max")] + public static object GetMaximum(EachStorage/*!*/ comparisonStorage, Range/*!*/ self) { + if (IsBeginGreatherThanEnd(comparisonStorage, self)) { + return null; + } + + if (!self.ExcludeEnd) { + return self.End; + } + + return EachInRange(comparisonStorage, self, 1).Last(); + } + + [RubyMethod("max")] + public static object GetMaximumBy(EachStorage/*!*/ comparisonStorage, BlockParam/*!*/ block, Range/*!*/ self) { + if (IsBeginGreatherThanEnd(comparisonStorage, self)) { + return null; + } + + var each = EachInRange(comparisonStorage, self, 1); + + return each.Aggregate((x, y) => { + object blockResult; + if (block.Yield(y, x, out blockResult)) { + return blockResult; + } + + int r = Protocols.ConvertCompareResult(comparisonStorage, blockResult); + + return r < 0 ? x : y; + }); + } + + [RubyMethod("min")] + public static object GetMinimum(EachStorage/*!*/ comparisonStorage, Range/*!*/ self) { + if (IsBeginGreatherThanEnd(comparisonStorage, self)) { + return null; + } + + return self.Begin; + } + + [RubyMethod("min")] + public static object GetMinimumBy(EachStorage/*!*/ comparisonStorage, BlockParam/*!*/ block, Range/*!*/ self) { + if (IsBeginGreatherThanEnd(comparisonStorage, self)) { + return null; + } + + var each = EachInRange(comparisonStorage, self, 1); + + return each.Aggregate((x, y) => { + object blockResult; + if (block.Yield(y, x, out blockResult)) { + return blockResult; + } + + int r = Protocols.ConvertCompareResult(comparisonStorage, blockResult); + + return r < 0 ? y : x; + }); + } + + #endregion + } +} diff --git a/platform/wp7/IronRuby/Languages/Ruby/Ruby/Builtins/RubyEncoding.cs b/platform/wp7/IronRuby/Languages/Ruby/Ruby/Builtins/RubyEncoding.cs new file mode 100644 index 00000000000..968665830d9 --- /dev/null +++ b/platform/wp7/IronRuby/Languages/Ruby/Ruby/Builtins/RubyEncoding.cs @@ -0,0 +1,678 @@ +/* **************************************************************************** + * + * Copyright (c) Microsoft Corporation. + * + * This source code is subject to terms and conditions of the Apache License, Version 2.0. A + * copy of the license can be found in the License.html file at the root of this distribution. If + * you cannot locate the Apache License, Version 2.0, please send an email to + * ironruby@microsoft.com. By using this source code in any fashion, you are agreeing to be bound + * by the terms of the Apache License, Version 2.0. + * + * You must not remove this notice, or any other, from this software. + * + * + * ***************************************************************************/ + +#if !CLR2 +using System.Linq.Expressions; +#else +using Microsoft.Scripting.Ast; +#endif + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; +using System.Threading; +using System.Runtime.Serialization; +using System.Security.Permissions; +using Microsoft.Scripting.Utils; +using Microsoft.Scripting.Runtime; +using IronRuby.Runtime; +using IronRuby.Compiler; + +namespace IronRuby.Builtins { + + /// + /// All encodings in Ruby are represented by instances of a single class. Therefore we need to wrap .NET Encoding class in RubyEncoding. + /// Instances of this class are app-domain singletons. That's all right as far as the class is readonly and doesn't implement IRubyObject. + /// Taint, frozen flags and instance variables need to be stored in per-runtime lookaside table. + /// + [Serializable] + public class RubyEncoding : ISerializable, IExpressionSerializable { + #region Singletons + + public const int CodePageBinary = 0; + public const int CodePageSJIS = 932; + public const int CodePageBig5 = 950; + public const int CodePageAscii = 20127; + + // Windows returns 2 EUC-JP encodings (CP 20932 and CP 51932). Mono implements EUC-JP as 51932 and doesn't support 20932. + public const int CodePageEUCJP = 51932; + + public const int CodePageUTF7 = 65000; + public const int CodePageUTF8 = 65001; + public const int CodePageUTF16BE = 1201; + public const int CodePageUTF16LE = 1200; + public const int CodePageUTF32BE = 12001; + public const int CodePageUTF32LE = 12000; + + // TODO: how does MRI sort encodings? + + public static readonly RubyEncoding/*!*/ Binary = new RubyEncoding(BinaryEncoding.Instance, BinaryEncoding.Instance, -4); + public static readonly RubyEncoding/*!*/ UTF8 = new RubyEncoding(CreateEncoding(CodePageUTF8, false), CreateEncoding(CodePageUTF8, true), -3); + +#if SILVERLIGHT + public static readonly RubyEncoding/*!*/ Ascii = UTF8; +#else + public static readonly RubyEncoding/*!*/ Ascii = new RubyEncoding(CreateEncoding(CodePageAscii, false), CreateEncoding(CodePageAscii, true), -2); + public static readonly RubyEncoding/*!*/ EUCJP = new RubyEncoding(CreateEncoding(CodePageEUCJP, false), CreateEncoding(CodePageEUCJP, true), -1); + public static readonly RubyEncoding/*!*/ SJIS = new RubyEncoding(CreateEncoding(CodePageSJIS, false), CreateEncoding(CodePageSJIS, true), 0); +#endif + + #endregion + + private readonly Encoding/*!*/ _encoding; + private readonly Encoding/*!*/ _strictEncoding; + private Expression _expression; + private readonly int _ordinal; + + // TODO: combine into a single integer (tables could be merged) + private readonly int _maxBytesPerChar; + private readonly bool _isAsciiIdentity; +#if !SILVERLIGHT + private bool? _isSingleByteCharacterSet; + private bool? _isDoubleByteCharacterSet; +#endif + + private RubyEncoding(Encoding/*!*/ encoding, Encoding/*!*/ strictEncoding, int ordinal) { + Assert.NotNull(encoding, strictEncoding); + _ordinal = ordinal; + _encoding = encoding; + _strictEncoding = strictEncoding; + _maxBytesPerChar = strictEncoding.GetMaxByteCount(1); + _isAsciiIdentity = AsciiIdentity(encoding); + } + + public override int GetHashCode() { + return _ordinal; + } + + internal Expression/*!*/ Expression { + get { return _expression ?? (_expression = Expression.Constant(this)); } + } + + public bool IsAsciiIdentity { + get { return _isAsciiIdentity; } + } + + private static Encoding/*!*/ CreateEncoding(int codepage, bool throwOnError) { +#if SILVERLIGHT + return new UTF8Encoding(false, throwOnError); +#else + if (throwOnError) { + return Encoding.GetEncoding(codepage, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); + } else { + return Encoding.GetEncoding(codepage, EncoderFallback.ReplacementFallback, BinaryDecoderFallback.Instance); + } +#endif + } + + #region Serialization +#if !SILVERLIGHT + private RubyEncoding(SerializationInfo/*!*/ info, StreamingContext context) { + throw Assert.Unreachable; + } + + [Serializable] + internal sealed class Deserializer : ISerializable, IObjectReference { + private readonly int _codePage; + + private Deserializer(SerializationInfo/*!*/ info, StreamingContext context) { + _codePage = info.GetInt32("CodePage"); + } + + public object GetRealObject(StreamingContext context) { + return GetRubyEncoding(_codePage); + } + + void ISerializable.GetObjectData(SerializationInfo/*!*/ info, StreamingContext context) { + throw Assert.Unreachable; + } + } + + void ISerializable.GetObjectData(SerializationInfo/*!*/ info, StreamingContext context) { + info.AddValue("CodePage", CodePage); + info.SetType(typeof(Deserializer)); + } +#endif + #endregion + + public int MaxBytesPerChar { + get { return _maxBytesPerChar; } + } + + public Encoding/*!*/ Encoding { + get { return _encoding; } + } + + public Encoding/*!*/ StrictEncoding { + get { return _strictEncoding; } + } + + /// + /// Name as displayed by MRI. + /// + public string/*!*/ Name { + get { + return GetRubySpecificName(CodePage) ?? _encoding.WebName; + } + } + + public static string GetRubySpecificName(int codepage) { + switch (codepage) { + case RubyEncoding.CodePageUTF8: return "UTF-8"; +#if !SILVERLIGHT + case RubyEncoding.CodePageUTF7: return "UTF-7"; + case RubyEncoding.CodePageUTF16BE: return "UTF-16BE"; + case RubyEncoding.CodePageUTF16LE: return "UTF-16LE"; + case RubyEncoding.CodePageUTF32BE: return "UTF-32BE"; + case RubyEncoding.CodePageUTF32LE: return "UTF-32LE"; + case RubyEncoding.CodePageSJIS: return "Shift_JIS"; + case RubyEncoding.CodePageAscii: return "US-ASCII"; + + // disambiguates CP 20932 and CP 51932: + case RubyEncoding.CodePageEUCJP: return "EUC-JP"; + case 20932: return "CP20932"; + + case 50220: return "ISO-2022-JP"; + case 50222: return "CP50222"; +#endif + default: return null; + } + } + + public int CodePage { + get { return GetCodePage(_encoding); } + } + + public override string/*!*/ ToString() { + return Name; + } + + public int CompareTo(RubyEncoding/*!*/ other) { + return _ordinal - other._ordinal; + } + + public static RubyRegexOptions ToRegexOption(RubyEncoding encoding) { + if (encoding == RubyEncoding.Binary) { + return RubyRegexOptions.FIXED; + } + + if (encoding == null) { + return RubyRegexOptions.NONE; + } + + switch (encoding.CodePage) { +#if !SILVERLIGHT + case RubyEncoding.CodePageSJIS: return RubyRegexOptions.SJIS; + case RubyEncoding.CodePageEUCJP: return RubyRegexOptions.EUC; +#endif + case RubyEncoding.CodePageUTF8: return RubyRegexOptions.UTF8; + } + + throw Assert.Unreachable; + } + + public static RubyEncoding GetRegexEncoding(RubyRegexOptions options) { + switch (options & RubyRegexOptions.EncodingMask) { +#if !SILVERLIGHT + case RubyRegexOptions.EUC: return RubyEncoding.EUCJP; + case RubyRegexOptions.SJIS: return RubyEncoding.SJIS; +#endif + case RubyRegexOptions.UTF8: return RubyEncoding.UTF8; + case RubyRegexOptions.FIXED: return RubyEncoding.Binary; + default: return null; + } + } + + internal static int GetCodePage(int nameInitial) { + switch (nameInitial) { +#if !SILVERLIGHT + case 'E': + case 'e': return CodePageEUCJP; + case 'S': + case 's': return CodePageSJIS; +#endif + case 'U': + case 'u': return CodePageUTF8; + default: + return -1; + } + } + + public static RubyEncoding GetEncodingByNameInitial(int initial) { + int codepage = GetCodePage(initial); + return codepage > 0 ? GetRubyEncoding(codepage) : null; + } + + public void RequireAsciiIdentity() { + if (!_isAsciiIdentity) { + throw new NotSupportedException(String.Format("Encoding {0} (code page {1}) is not supported", Name, CodePage)); + } + } + +#if !SILVERLIGHT + private static Dictionary _Encodings; + + public static RubyEncoding/*!*/ GetRubyEncoding(Encoding/*!*/ encoding) { + ContractUtils.RequiresNotNull(encoding, "encoding"); + + if (encoding.CodePage == 0) { + if (encoding == BinaryEncoding.Instance) { + return Binary; + } + + // TODO: allow custom encodings (without codepage) + } + + return GetRubyEncoding(encoding.CodePage); + } + + public static RubyEncoding/*!*/ GetRubyEncoding(int codepage) { + switch (codepage) { + case CodePageBinary: return Binary; + case CodePageAscii: return Ascii; + case CodePageUTF8: return UTF8; + case CodePageSJIS: return SJIS; + case CodePageEUCJP: return EUCJP; + } + + if (_Encodings == null) { + Interlocked.CompareExchange(ref _Encodings, new Dictionary(), null); + } + + RubyEncoding result; + lock (_Encodings) { + if (!_Encodings.TryGetValue(codepage, out result)) { + result = new RubyEncoding( + CreateEncoding(codepage, false), + CreateEncoding(codepage, true), + codepage + ); + + _Encodings.Add(codepage, result); + } + } + + return result; + } + + private static int GetCodePage(Encoding/*!*/ encoding) { + return encoding.CodePage; + } + + public static bool AsciiIdentity(Encoding/*!*/ encoding) { + if (encoding == BinaryEncoding.Instance) { + return true; + } + + switch (encoding.CodePage) { + case 437: // OEM United States + case 708: // Arabic (ASMO 708) + case 720: // Arabic (DOS) + case 737: // Greek (DOS) + case 775: // Baltic (DOS) + case 850: // Western European (DOS) + case 852: // Central European (DOS) + case 855: // OEM Cyrillic + case 857: // Turkish (DOS) + case 858: // OEM Multilingual Latin I + case 860: // Portuguese (DOS) + case 861: // Icelandic (DOS) + case 862: // Hebrew (DOS) + case 863: // French Canadian (DOS) + case 864: // Arabic (864) + case 865: // Nordic (DOS) + case 866: // Cyrillic (DOS) + case 869: // Greek, Modern (DOS) + case 874: // Thai (Windows) + case 932: // Japanese (Shift-JIS) + case 936: // Chinese Simplified (GB2312) + case 949: // Korean + case 950: // Chinese Traditional (Big5) + case 1250: // Central European (Windows) + case 1251: // Cyrillic (Windows) + case 1252: // Western European (Windows) + case 1253: // Greek (Windows) + case 1254: // Turkish (Windows) + case 1255: // Hebrew (Windows) + case 1256: // Arabic (Windows) + case 1257: // Baltic (Windows) + case 1258: // Vietnamese (Windows) + case 1361: // Korean (Johab) + case 10000: // Western European (Mac) + case 10001: // Japanese (Mac) + case 10002: // Chinese Traditional (Mac) + case 10003: // Korean (Mac) + case 10004: // Arabic (Mac) + case 10005: // Hebrew (Mac) + case 10006: // Greek (Mac) + case 10007: // Cyrillic (Mac) + case 10008: // Chinese Simplified (Mac) + case 10010: // Romanian (Mac) + case 10017: // Ukrainian (Mac) + case 10029: // Central European (Mac) + case 10079: // Icelandic (Mac) + case 10081: // Turkish (Mac) + case 10082: // Croatian (Mac) + case 20000: // Chinese Traditional (CNS) + case 20001: // TCA Taiwan + case 20002: // Chinese Traditional (Eten) + case 20003: // IBM5550 Taiwan + case 20004: // TeleText Taiwan + case 20005: // Wang Taiwan + case 20127: // US-ASCII + case 20866: // Cyrillic (KOI8-R) + case 20932: // Japanese (JIS 0208-1990 and 0212-1990) + case 20936: // Chinese Simplified (GB2312-80) + case 20949: // Korean Wansung + case 21866: // Cyrillic (KOI8-U) + case 28591: // Western European (ISO) + case 28592: // Central European (ISO) + case 28593: // Latin 3 (ISO) + case 28594: // Baltic (ISO) + case 28595: // Cyrillic (ISO) + case 28596: // Arabic (ISO) + case 28597: // Greek (ISO) + case 28598: // Hebrew (ISO-Visual) + case 28599: // Turkish (ISO) + case 28603: // Estonian (ISO) + case 28605: // Latin 9 (ISO) + case 38598: // Hebrew (ISO-Logical) + case 50220: // Japanese (JIS) + case 50221: // Japanese (JIS-Allow 1 byte Kana) + case 50222: // Japanese (JIS-Allow 1 byte Kana - SO/SI) + case 50225: // Korean (ISO) + case 50227: // Chinese Simplified (ISO-2022) + case 51932: // Japanese (EUC) + case 51936: // Chinese Simplified (EUC) + case 51949: // Korean (EUC) + case 54936: // Chinese Simplified (GB18030) + case 57002: // ISCII Devanagari + case 57003: // ISCII Bengali + case 57004: // ISCII Tamil + case 57005: // ISCII Telugu + case 57006: // ISCII Assamese + case 57007: // ISCII Oriya + case 57008: // ISCII Kannada + case 57009: // ISCII Malayalam + case 57010: // ISCII Gujarati + case 57011: // ISCII Punjabi + case 65001: // Unicode (UTF-8) + Debug.Assert(IsAsciiIdentityFallback(encoding)); + return true; + + default: + return IsAsciiIdentityFallback(encoding); + } + } + + private static string _AllAscii; + + private static bool IsAsciiIdentityFallback(Encoding/*!*/ encoding) { + if (_AllAscii == null) { + // all ASCII characters: + var sb = new StringBuilder(0x80); + for (int i = 0; i < 0x80; i++) { + sb.Append((char)i); + } + _AllAscii = sb.ToString(); + } + + var bytes = encoding.GetBytes(_AllAscii); + if (bytes.Length != _AllAscii.Length) { + return false; + } + + for (int i = 0; i < _AllAscii.Length; i++) { + if ((int)_AllAscii[i] != (int)bytes[i]) { + return false; + } + } + + return true; + } + + public bool IsSingleByteCharacterSet { + get { + if (!_isSingleByteCharacterSet.HasValue) { + _isSingleByteCharacterSet = IsSBCS(CodePage); + } + + return _isSingleByteCharacterSet.Value; + } + } + + public bool IsDoubleByteCharacterSet { + get { + if (!_isDoubleByteCharacterSet.HasValue) { + _isDoubleByteCharacterSet = IsDBCS(CodePage); + } + + return _isDoubleByteCharacterSet.Value; + } + } + + private static int[] _sbsc; + private static int[] _dbsc; + + private static bool IsSBCS(int codepage) { + if (_sbsc == null) { + _sbsc = new int[] { + 0, 37, 437, 500, 708, 720, 737, 775, 850, 852, 855, 857, 858, 860, 861, 862, 863, 864, 865, 866, 869, 870, 874, 875, 1026, + 1047, 1140, 1141, 1142, 1143, 1144, 1145, 1146, 1147, 1148, 1149, 1250, 1251, 1252, 1253, 1254, 1255, 1256, 1257, 1258, + 10000, 10004, 10005, 10006, 10007, 10010, 10017, 10021, 10029, 10079, 10081, 10082, 20105, 20106, 20107, 20108, 20127, + 20269, 20273, 20277, 20278, 20280, 20284, 20285, 20290, 20297, 20420, 20423, 20424, 20833, 20838, 20866, 20871, 20880, + 20905, 20924, 21025, 21866, 28592, 28593, 28594, 28595, 28596, 28597, 28598, 28599, 28603, 28605, 29001, 38598 + }; + } + + return Array.BinarySearch(_sbsc, codepage) >= 0; + } + + private static bool IsDBCS(int codepage) { + if (_dbsc == null) { + _dbsc = new int[] { + 932, 936, 949, 950, 1361, 10001, 10002, 10003, 10008, 20000, 20001, 20002, 20003, 20004, 20005, 20261, 20932, 20936, + 20949, 50227, 51936, 51949 + }; + } + + return Array.BinarySearch(_dbsc, codepage) >= 0; + } + + public bool InUnicodeBasicPlane { + get { + // TODO: others + return this == Ascii || this == Binary; + } + } + + public bool IsUnicodeEncoding { + get { + switch (CodePage) { + case CodePageUTF7: + case CodePageUTF8: + case CodePageUTF16BE: + case CodePageUTF16LE: + case CodePageUTF32BE: + case CodePageUTF32LE: + return true; + } + return false; + } + } + + private static ReadOnlyDictionary _aliases; + + public static ReadOnlyDictionary Aliases { + get { return _aliases ?? (_aliases = CreateAliases()); } + } + + private static ReadOnlyDictionary CreateAliases() { + return new ReadOnlyDictionary(new Dictionary(StringComparer.InvariantCultureIgnoreCase) { + { "646", "US-ASCII" }, + { "ASCII", "US-ASCII" }, + { "ANSI_X3.4-1968", "US-ASCII" }, + { "BINARY", "ASCII-8BIT" }, + { "CP437", "IBM437" }, + { "CP737", "IBM737" }, + { "CP775", "IBM775" }, + { "CP857", "IBM857" }, + { "CP860", "IBM860" }, + { "CP861", "IBM861" }, + { "CP862", "IBM862" }, + { "CP863", "IBM863" }, + { "CP864", "IBM864" }, + { "CP865", "IBM865" }, + { "CP866", "IBM866" }, + { "CP869", "IBM869" }, + { "CP874", "Windows-874" }, + { "CP878", "KOI8-R" }, + { "CP932", "Windows-31J" }, + { "CP936", "GBK" }, + { "CP950", "Big5" }, + { "CP951", "Big5-HKSCS" }, + { "CP1258", "Windows-1258" }, + { "CP1252", "Windows-1252" }, + { "CP1250", "Windows-1250" }, + { "CP1256", "Windows-1256" }, + { "CP1251", "Windows-1251" }, + { "CP1253", "Windows-1253" }, + { "CP1255", "Windows-1255" }, + { "CP1254", "Windows-1254" }, + { "CP1257", "Windows-1257" }, + { "CP65000", "UTF-7" }, + { "CP65001", "UTF-8" }, + { "IBM850", "CP850" }, + { "eucJP", "EUC-JP" }, + { "eucKR", "EUC-KR" }, + // { "eucTW", "EUC-TW" }, + { "ISO2022-JP", "ISO-2022-JP" }, + // { "ISO2022-JP2", "ISO-2022-JP-2" }, + { "ISO8859-1", "ISO-8859-1" }, + { "ISO8859-2", "ISO-8859-2" }, + { "ISO8859-3", "ISO-8859-3" }, + { "ISO8859-4", "ISO-8859-4" }, + { "ISO8859-5", "ISO-8859-5" }, + { "ISO8859-6", "ISO-8859-6" }, + { "ISO8859-7", "ISO-8859-7" }, + { "ISO8859-8", "ISO-8859-8" }, + { "ISO8859-9", "ISO-8859-9" }, + // { "ISO8859-10", "ISO-8859-10" }, + { "ISO8859-11", "ISO-8859-11" }, + { "ISO8859-13", "ISO-8859-13" }, + // { "ISO8859-14", "ISO-8859-14" }, + { "ISO8859-15", "ISO-8859-15" }, + // { "ISO8859-16", "ISO-8859-16" }, + { "SJIS", "Shift_JIS" }, + { "csWindows31J", "Windows-31J" }, + // { "MacJapan", "MacJapanese" }, + // { "UTF-8-MAC", "UTF8-MAC" }, + // { "UTF-8-HFS", "UTF8-MAC" }, + { "UCS-2BE", "UTF-16BE" }, + { "UCS-4BE", "UTF-32BE" }, + { "UCS-4LE", "UTF-32LE" }, + }); + } + + Expression/*!*/ IExpressionSerializable.CreateExpression() { + // TODO: use static fields + return Methods.CreateEncoding.OpCall(Expression.Constant(CodePage)); + } +#else + public static bool AsciiIdentity(Encoding/*!*/ encoding) { + switch (GetCodePage(encoding)) { + case CodePageBinary: + case CodePageUTF8: + return true; + } + + return false; + } + + public bool IsSingleByteCharacterSet { + get { + return this == Binary; + } + } + + public bool IsDoubleByteCharacterSet { + get { + return false; + } + } + + public bool InUnicodeBasicPlane { + get { + return this == Binary; + } + } + + public static RubyEncoding/*!*/ GetRubyEncoding(Encoding/*!*/ encoding) { + ContractUtils.RequiresNotNull(encoding, "encoding"); +//RHO +/* + if (encoding == BinaryEncoding.Instance) { + return Binary; + }else if (encoding == Encoding.UTF8 || encoding == System.Text.UTF8Encoding.UTF8) + { + return UTF8; + } else { + throw new ArgumentException(String.Format("Unknown encoding: '{0}'", encoding)); + } + */ + switch (GetCodePage(encoding)) + { + case CodePageBinary: return Binary; + case CodePageAscii: return Ascii; + case CodePageUTF8: return UTF8; + } + + throw new ArgumentException(String.Format("Unknown encoding: '{0}'", encoding)); +//RHO + } + + internal static RubyEncoding/*!*/ GetRubyEncoding(int codepage) { + switch (codepage) { + case CodePageBinary: return Binary; + case CodePageUTF8: return UTF8; + default: throw new ArgumentException(String.Format("Unknown encoding codepage: {0}", codepage)); + } + } + + private static int GetCodePage(Encoding/*!*/ encoding) { + Debug.Assert(encoding != null); + + if (encoding == BinaryEncoding.Instance) { + return CodePageBinary; + } + + switch (encoding.WebName.ToUpperInvariant()) { + case "UTF-8": return CodePageUTF8; + case "UTF-16": return CodePageUTF16LE; + case "UTF-16BE": return CodePageUTF16BE; + } + + throw new ArgumentException(String.Format("Unknown encoding: {0}", encoding)); + } + + Expression/*!*/ IExpressionSerializable.CreateExpression() { + // TODO: use a static fields, deal with KCODEs + return Expression.Constant(UTF8); + } +#endif + } + +} diff --git a/platform/wp7/IronRuby/Languages/Ruby/Ruby/Runtime/RubyExceptionData.cs b/platform/wp7/IronRuby/Languages/Ruby/Ruby/Runtime/RubyExceptionData.cs new file mode 100644 index 00000000000..f4cb19187ff --- /dev/null +++ b/platform/wp7/IronRuby/Languages/Ruby/Ruby/Runtime/RubyExceptionData.cs @@ -0,0 +1,271 @@ +/* **************************************************************************** + * + * Copyright (c) Microsoft Corporation. + * + * This source code is subject to terms and conditions of the Apache License, Version 2.0. A + * copy of the license can be found in the License.html file at the root of this distribution. If + * you cannot locate the Apache License, Version 2.0, please send an email to + * ironruby@microsoft.com. By using this source code in any fashion, you are agreeing to be bound + * by the terms of the Apache License, Version 2.0. + * + * You must not remove this notice, or any other, from this software. + * + * + * ***************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Security; +using System.Security.Permissions; +using System.Threading; +using IronRuby.Builtins; +using IronRuby.Runtime.Calls; +using Microsoft.Scripting; +using Microsoft.Scripting.Interpreter; +using Microsoft.Scripting.Utils; +using System.Globalization; + +namespace IronRuby.Runtime { + /// + /// Stores extra instance data associated with Ruby exceptions + /// + [Serializable] + public sealed class RubyExceptionData { + private static readonly object/*!*/ _DataKey = typeof(RubyExceptionData); + + // An exception class can implement singleton method "new" that returns an arbitrary instance of an exception. + // This mapping needs to be applied on exceptions created in libraries as well (they should be created "dynamically"). + // That would however need to pass RubyContext to every method that might throw an exception. Instead, we call + // "new" on the exception's class as soon as it gets to the first Ruby EH handler (rescue/ensure/else). + // + // True if the exception has already been handled by Ruby EH clause or if it was constructed "dynamically" via Class#new. + internal bool Handled { get; set; } + + // Real exception begin propagated by the CLR. Needed for lazy initialization of message, backtrace + private Exception/*!*/ _exception; + // For asynchronous exceptions (Thread#raise), the user-visible exception (accessible via _visibleException) + // is wrapped in a TheadAbortException (accessible via _exception) + private Exception/*!*/ _visibleException; + +#if DEBUG +#pragma warning disable 414 // msc: unused field + // For asynchronous exceptions, this is useful to figure out which thread raised the exception + [NonSerialized] + private Thread/*!*/ _throwingThread; +#pragma warning restore 414 +#endif + + // if this is set to null we need to initialize it + private object _message; + + // can be set explicitly by the user (even to nil): + private RubyArray _backtrace; + + //RHO + private static RubyArray m_orig_backtrace; + private static object m_orig_message; + //RHO + + [NonSerialized] + private CallSite> _setBacktraceCallSite; + + private RubyExceptionData(Exception/*!*/ exception) { + _exception = exception; + _visibleException = exception; +#if DEBUG + _throwingThread = Thread.CurrentThread; +#endif + } + + public static RubyArray/*!*/ CreateBacktrace(RubyContext/*!*/ context, int skipFrames) { + return new RubyStackTraceBuilder(context, skipFrames).RubyTrace; + } + + /// + /// Builds backtrace for the exception if it wasn't built yet. + /// Captures a full stack trace starting with the current frame and combines it with the trace of the exception. + /// Called from compiled code. + /// + internal void CaptureExceptionTrace(RubyScope/*!*/ scope) { + if (_backtrace == null) { + StackTrace catchSiteTrace = RubyStackTraceBuilder.GetClrStackTrace(null); + _backtrace = new RubyStackTraceBuilder(scope.RubyContext, _exception, catchSiteTrace, scope.InterpretedFrame != null).RubyTrace; + DynamicSetBacktrace(scope.RubyContext, _backtrace); + } + } + + /// + /// This is called by the IronRuby runtime to set the backtrace for an exception that has being raised. + /// Note that the backtrace may be set directly by user code as well. However, that uses a different code path. + /// + private void DynamicSetBacktrace(RubyContext/*!*/ context, RubyArray backtrace) { + if (_setBacktraceCallSite == null) { + Interlocked.CompareExchange(ref _setBacktraceCallSite, CallSite>. + Create(RubyCallAction.MakeShared("set_backtrace", RubyCallSignature.WithImplicitSelf(1))), null); + } + _setBacktraceCallSite.Target(_setBacktraceCallSite, context, _exception, backtrace); + } + + /// + /// Gets the instance data associated with the exception + /// + public static RubyExceptionData/*!*/ GetInstance(Exception/*!*/ e) { + RubyExceptionData result = TryGetInstance(e); + if (result == null) { + result = AssociateInstance(e); + } + return result; + } + + internal static RubyExceptionData/*!*/ AssociateInstance(Exception/*!*/ e) { + RubyExceptionData result; + + Exception visibleException = RubyUtils.GetVisibleException(e); + if (e == visibleException || visibleException == null) { + result = new RubyExceptionData(e); + } else { + // Async exception + + Debug.Assert(e is ThreadAbortException); + result = GetInstance(visibleException); + + if (result._exception == visibleException) { + // A different instance of ThreadAbortException is thrown at the end of every catch block (as long as + // Thread.ResetAbort is not called). However, we only want to remember the first one + // as it will have the most complete stack trace. + result._exception = e; + } + } + + e.Data[_DataKey] = result; + return result; + } + + internal static RubyExceptionData TryGetInstance(Exception/*!*/ e) { + return e.Data[_DataKey] as RubyExceptionData; + } + + public object Message { + get { + //RHO + if (m_orig_message != null) + return m_orig_message; + //RHO + + if (_message == null) { + _message = MutableString.Create(_visibleException.Message, RubyEncoding.UTF8); + } + return _message; + } + set { + ContractUtils.RequiresNotNull(value, "value"); + _message = value; + } + } + + public RubyArray Backtrace { + get { + //RHO + if (m_orig_backtrace != null) + return m_orig_backtrace; + //RHO + return _backtrace; + } + set { + //RHO + if (!_visibleException.Message.Equals("TargetInvocationException")) + { + m_orig_backtrace = _backtrace; + m_orig_message = _visibleException.Message; + } + //RHO + _backtrace = value; + } + } + + public static string/*!*/ GetClrMessage(RubyContext/*!*/ context, object message) { + return Protocols.ToClrStringNoThrow(context, message); + } + + public static string/*!*/ GetClrMessage(RubyClass/*!*/ exceptionClass, object message) { + return GetClrMessage(exceptionClass.Context, message ?? exceptionClass.Name); + } + + public static Exception/*!*/ InitializeException(Exception/*!*/ exception, object message) { + RubyExceptionData data = RubyExceptionData.GetInstance(exception); + // only set it if message is non-null. Otherwise, let lazy initialization create the default message from CLR exception message + if (message != null) { + data.Message = message; + } + + return exception; + } + + internal static Exception/*!*/ HandleException(RubyContext/*!*/ context, Exception/*!*/ exception) { + // already handled: + var instanceData = GetInstance(exception); + if (instanceData.Handled) { + return exception; + } + + RubyClass exceptionClass = context.GetClass(exception.GetType()); + + // new resolves to Class#new built-in method: + var newMethod = exceptionClass.ImmediateClass.ResolveMethod("new", VisibilityContext.AllVisible); + if (newMethod.Found && newMethod.Info.DeclaringModule == context.ClassClass && newMethod.Info is RubyCustomMethodInfo) { + // initialize resolves to a built-in method: + var initializeMethod = exceptionClass.ResolveMethod("initialize", VisibilityContext.AllVisible); + if (initializeMethod.Found && initializeMethod.Info is RubyLibraryMethodInfo) { + instanceData.Handled = true; + return exception; + } + } + + var site = exceptionClass.NewSite; + Exception newException; + try { + newException = site.Target(site, exceptionClass, instanceData.Message) as Exception; + } catch (Exception e) { + // MRI: this can lead to stack overflow: + return HandleException(context, e); + } + + // MRI doesn't handle this correctly, see http://redmine.ruby-lang.org/issues/show/1886: + if (newException == null) { + newException = RubyExceptions.CreateTypeError("exception object expected"); + } + + var newInstanceData = GetInstance(newException); + + newInstanceData.Handled = true; + newInstanceData._backtrace = instanceData._backtrace; + return newException; + } + +#if SILVERLIGHT // Thread.ExceptionState + public static void ActiveExceptionHandled(Exception visibleException) {} +#else + public static void ActiveExceptionHandled(Exception visibleException) { + Debug.Assert(RubyUtils.GetVisibleException(visibleException) == visibleException); + + RubyExceptionData data = RubyExceptionData.GetInstance(visibleException); + if (data._exception != visibleException) { + // The exception was raised asynchronously with Thread.Abort. We can not just catch and ignore + // the ThreadAbortException as the CLR keeps trying to re-raise it unless ResetAbort is called. + // + // Note that ResetAbort can cause ThreadAbortException.ExceptionState to be cleared (though it may + // not be cleared under some circustances), and we use that to squirrel away the Ruby exception + // that the user is expecting. Hence, ResetAbort should only be called when + // ThreadAbortException.ExceptionState no longer needs to be accessed. + if ((Thread.CurrentThread.ThreadState & System.Threading.ThreadState.AbortRequested) != 0) { + Thread.ResetAbort(); + } + } + } +#endif + } +} diff --git a/platform/wp7/IronRuby/Languages/Ruby/Ruby/Runtime/RubyUtils.cs b/platform/wp7/IronRuby/Languages/Ruby/Ruby/Runtime/RubyUtils.cs index 821a5806a55..51a414518f6 100644 --- a/platform/wp7/IronRuby/Languages/Ruby/Ruby/Runtime/RubyUtils.cs +++ b/platform/wp7/IronRuby/Languages/Ruby/Ruby/Runtime/RubyUtils.cs @@ -978,8 +978,10 @@ private class RecursionHandle : IDisposable { Type baseType = theclass.GetUnderlyingSystemType(); object obj; +#if SILVERLIGHT // serialization + if (typeof(ISerializable).IsAssignableFrom(baseType) && !typeof(RubyObject).IsAssignableFrom(baseType)) { +#else if (typeof(ISerializable).IsAssignableFrom(baseType)) { -#if !SILVERLIGHT // serialization BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; ConstructorInfo ci = baseType.GetConstructor(bindingFlags, null, _serializableTypeSignature, null); if (ci == null) { @@ -1185,6 +1187,34 @@ private class ThreadExitMarker { return path[0] == '/'; } + public static bool IsRelativeToCurrentDirectory(string path) { + if (String.IsNullOrEmpty(path)) { + return false; + } + + if (path.Length == 1) { + return path[0] == '.'; + } + + if (path[0] != '.') { + return false; + } + + if (path[1] == '/' || path[1] == '\\') { + return true; + } + + if (path.Length == 2) { + return path[1] == '.'; + } + + if (path[2] == '/' || path[2] == '\\') { + return true; + } + + return false; + } + // Is path something like "c:/foo/bar" (on Windows) public static bool IsAbsoluteDriveLetterPath(string path) { if (String.IsNullOrEmpty(path)) {