Click here to Skip to main content
15,892,298 members
Articles / Programming Languages / C# 4.0

The List Trifecta, Part 2

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
7 Sep 2013LGPL310 min read 28.7K   317   12  
The BDictionary is like a Dictionary mashed up with List<T>. BList and BMultiMap also say hello.
//
// Standard message sinks:
//     ConsoleMessageSink, 
//     NullMessageSink,
//     TraceMessageSink,
//     MessageHolder,
//     MessageFilter,
//     MulticastMessageSink
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Loyc.Collections.Impl;

namespace Loyc.Utilities
{
	/// <summary>Sends all messages to the <see cref="System.Console.WriteLine"/>, 
	/// with hard-coded colors for Error, Warning, Note, Verbose, and Detail.</summary>
	public class ConsoleMessageSink : IMessageSink
	{
		protected static ConsoleColor _lastColor;

		protected virtual ConsoleColor PickColor(Symbol msgType, out string msgTypeText)
		{
			bool implicitText = false;
			ConsoleColor color;

			if (msgType == MessageSink.Error)
			{
				color = ConsoleColor.Red;
				implicitText = true;
			}
			else if (msgType == MessageSink.Warning)
			{
				color = ConsoleColor.Yellow;
				implicitText = true;
			}
			else if (msgType == MessageSink.Note)
				color = ConsoleColor.White;
			else if (msgType == MessageSink.Verbose)
				color = ConsoleColor.Gray;
			else if (msgType == MessageSink.Detail) {
				switch (_lastColor)
				{
					case ConsoleColor.Red: color = ConsoleColor.DarkRed; break;
					case ConsoleColor.Yellow: color = ConsoleColor.DarkYellow; break;
					case ConsoleColor.White: color = ConsoleColor.Gray; break;
					case ConsoleColor.Green: color = ConsoleColor.DarkGreen; break;
					case ConsoleColor.Blue: color = ConsoleColor.DarkBlue; break;
					case ConsoleColor.Magenta: color = ConsoleColor.DarkMagenta; break;
					case ConsoleColor.Cyan: color = ConsoleColor.DarkCyan; break;
					default: color = ConsoleColor.DarkGray; break;
				}
				msgTypeText = null;
				return color;
			} else
				color = Console.ForegroundColor;

			msgTypeText = implicitText ? null : Localize.From(msgType.Name);
			_lastColor = color;
			return color;
		}

		public void Write(Symbol type, object context, string format)
		{
			WriteCore(type, context, Localize.From(format));
		}
		public void Write(Symbol type, object context, string format, object arg0, object arg1 = null)
		{
			WriteCore(type, context, Localize.From(format, arg0, arg1));
		}
		public void Write(Symbol type, object context, string format, params object[] args)
		{
			WriteCore(type, context, Localize.From(format, args));
		}
		void WriteCore(Symbol type, object context, string text)
		{
			string loc = MessageSink.LocationString(context);
			if (!string.IsNullOrEmpty(loc))
				Console.Write(loc + ": ");

			string typeText;
			ConsoleColor oldColor = Console.ForegroundColor;
			Console.ForegroundColor = PickColor(type, out typeText);
			if (typeText != null)
				text = typeText + ": " + text;
			Console.WriteLine(text);
			Console.ForegroundColor = oldColor;
		}
		/// <summary>Always returns true.</summary>
		public bool IsEnabled(Symbol type)
		{
			return true;
		}
	}

	/// <summary>Discards all messages.</summary>
	public sealed class NullMessageSink : IMessageSink
	{
		public void Write(Symbol type, object context, string format)
		{
		}
		public void Write(Symbol type, object context, string format, object arg0, object arg1 = null)
		{
		}
		public void Write(Symbol type, object context, string format, params object[] args)
		{
		}
		/// <summary>Always returns false.</summary>
		public bool IsEnabled(Symbol type)
		{
			return false;
		}
	}

	/// <summary>Sends all messages to <see cref="System.Diagnostics.Trace.WriteLine"/>.</summary>
	public class TraceMessageSink : IMessageSink
	{
		public void Write(Symbol type, object context, string format)
		{
			WriteCore(type, context, Localize.From(format));
		}
		public void Write(Symbol type, object context, string format, object arg0, object arg1 = null)
		{
			WriteCore(type, context, Localize.From(format, arg0, arg1));
		}
		public void Write(Symbol type, object context, string format, params object[] args)
		{
			WriteCore(type, context, Localize.From(format, args));
		}
		public void WriteCore(Symbol type, object context, string text)
		{
			string loc = MessageSink.LocationString(context);
			if (!string.IsNullOrEmpty(loc))
				text = loc + ": " + text;
			Trace.WriteLine(text, type.Name);
		}
		/// <summary>Always returns true.</summary>
		public bool IsEnabled(Symbol type)
		{
			return true;
		}
	}

	/// <summary>A message sink that stores all messages it receives.</summary>
	public class MessageHolder : IMessageSink
	{
		public struct Message : ILocationString
		{
			public Message(Symbol type, object context, string format, object arg0, object arg1 = null)
				: this (type, context, format, new object[2] { arg0, arg1 }) {}
			public Message(Symbol type, object context, string format)
				: this (type, context, format, InternalList<object>.EmptyArray) {}
			public Message(Symbol type, object context, string format, params object[] args)
			{
				Type = type ?? GSymbol.Empty;
				Context = context;
				Format = format;
				_args = args;
			}
			public readonly Symbol Type;
			public readonly object Context;
			public readonly string Format;
			readonly object[] _args;
			public object[] Args { get { return _args; } }
			public string Formatted
			{
				get { return Localize.From(Format, _args); }
			}
			public override string ToString()
			{
				string loc = LocationString;
				string text = Type.Name == "" ? Formatted : Type.Name + ": " + Formatted;
				return string.IsNullOrEmpty(loc) ? text : loc + ": " + text;
			}
			public string LocationString
			{
				get { return MessageSink.LocationString(Context); }
			}
			public void WriteTo(IMessageSink sink)
			{
				if (_args.Length == 0)
					sink.Write(Type, Context, Format);
				else
					sink.Write(Type, Context, Format, _args);
			}
		}
		List<Message> _messages;

		public IList<Message> List
		{
			get { return _messages = _messages ?? new List<Message>(); }
		}
		public void WriteListTo(IMessageSink sink)
		{
			foreach (Message msg in List)
				msg.WriteTo(sink);
		}

		public void Write(Symbol type, object context, string format)
		{
			List.Add(new Message(type, context, format));
		}
		public void Write(Symbol type, object context, string format, object arg0, object arg1 = null)
		{
			List.Add(new Message(type, context, format, arg0, arg1));
		}
		public void Write(Symbol type, object context, string format, params object[] args)
		{
			List.Add(new Message(type, context, format, args));
		}
		/// <summary>Always returns true.</summary>
		public bool IsEnabled(Symbol type)
		{
			return true;
		}
	}

	/// <summary>A decorator that uses a delegate to accept or ignore messages.</summary>
	/// <remarks>The filter can accept or reject messages based on both the message 
	/// type and the actual message (format string). When someone calls 
	/// <see cref="IsEnabled(Symbol)"/>, the filter is invoked with only the type;
	/// the message is set to null. Accepted messages are sent to the 
	/// <see cref="Target"/> message sink.</remarks>
	public class MessageFilter : IMessageSink
	{
		public Func<Symbol, object, string, bool> Filter { get; set; }
		public Func<Symbol, bool> TypeFilter { get; set; }
		public IMessageSink Target { get; set; }
		
		public MessageFilter(Func<Symbol, object, string, bool> filter, IMessageSink target) 
		{
			Filter = filter;
			Target = target;
		}
		public MessageFilter(Func<Symbol, bool> filter, IMessageSink target) 
		{
			TypeFilter = filter;
			Target = target;
		}
		bool Passes(Symbol type, object context, string format)
		{
			return Filter != null && Filter(type, context, format)
				|| TypeFilter != null && TypeFilter(type);
		}
		public void Write(Symbol type, object context, string format)
		{
			if (Passes(type, context, format))
				Target.Write(type, context, format);
		}
		public void Write(Symbol type, object context, string format, object arg0, object arg1 = null)
		{
			if (Passes(type, context, format))
				Target.Write(type, context, format, arg0, arg1);
		}
		public void Write(Symbol type, object context, string format, params object[] args)
		{
			if (Passes(type, context, format))
				Target.Write(type, context, format, args);
		}
		/// <summary>Returns true if <c>Filter(type, null)</c> and <c>target.IsEnabled(type)</c> are both true.</summary>
		public bool IsEnabled(Symbol type)
		{
			return Passes(type, null, null) && Target.IsEnabled(type);
		}
	}
	
	/// <summary>A message sink that sends its messages to a list of other sinks.</summary>
	public class MessageSplitter : IMessageSink
	{
		List<IMessageSink> _list = new List<IMessageSink>();
		public IList<IMessageSink> List { get { return _list; } }

		public MessageSplitter(IEnumerable<IMessageSink> targets) { _list = new List<IMessageSink>(targets); }
		public MessageSplitter(params IMessageSink[] targets) { _list = new List<IMessageSink>(targets); }
		public MessageSplitter() { _list = new List<IMessageSink>(); }
	
		public void  Write(Symbol type, object context, string format)
		{
 			foreach(var sink in _list)
				sink.Write(type, context, format);
		}
		public void  Write(Symbol type, object context, string format, object arg0, object arg1 = null)
		{
 			foreach(var sink in _list)
				sink.Write(type, context, format, arg0, arg1);
		}
		public void  Write(Symbol type, object context, string format, params object[] args)
		{
			foreach (var sink in _list)
				sink.Write(type, context, format, args);
		}
		/// <summary>Returns true if <tt>s.IsEnabled(type)</tt> is true for at least one target message sink 's'.</summary>
		public bool IsEnabled(Symbol type)
		{
			foreach (var sink in _list)
				if (sink.IsEnabled(type))
					return true;
			return false;
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Software Developer None
Canada Canada
Since I started programming when I was 11, I wrote the SNES emulator "SNEqr", the FastNav mapping component, the Enhanced C# programming language (in progress), the parser generator LLLPG, and LES, a syntax to help you start building programming languages, DSLs or build systems.

My overall focus is on the Language of your choice (Loyc) initiative, which is about investigating ways to improve interoperability between programming languages and putting more power in the hands of developers. I'm also seeking employment.

Comments and Discussions