Vamos a programar #66 - Expresiones regulares - Resaltador de sintaxis (POC).

Hola de nuevo a todos, el día de hoy vamos a continuar con un poco mas de expresiones regulares. Para seguir con el aprendizaje, veremos cómo hacer un programa en ## que servirá para resaltar la sintaxis de código fuente en C#.

El otro día mientras revisaba los mensajes de twitter, vi uno que me llamo la atencion:
Recuerden, anonimato ante todo!!.

Un usuario me pregunto si era posible crear una aplicación para resaltar la sintaxis de un código fuente y aprovechando que andamos en las expresiones regulares, dije: "por qué no?".

Para empezar, hay que recordar un poco de los fundamentos del lenguaje que queremos resaltar, por lo general está formado por componentes de la siguiente lista:

  • Palabras clave
    • Tipo uno
    • Tipo dos
    • Tipo tres
  • Números
  • Operadores
  • Cadenas de texto
  • Comentarios
En C# hay una lista de palabras reservadas, para fines prácticos, los podemos separar en instrucciones, tipos, modificadores, números, operadores, cadenas de texto y comentarios (espero no omitir nada), entonces podemos crear un expresión regular para cada grupo.
Una lista de las palabras reservadas en C# (sin separar) es esta:

  • as
  • base
  • bool
  • break
  • byte
  • case
  • catch
  • char
  • checked
  • class
  • continue
  • decimal
  • default
  • delegate
  • do
  • double
  • else
  • enum
  • explicit
  • false
  • finally
  • fixed
  • float
  • for
  • foreach
  • goto
  • if
  • implicit
  • in
  • int
  • interface
  • is
  • lock
  • long
  • namespace
  • null
  • object
  • operator
  • out
  • params
  • ref
  • return
  • sbyte
  • short
  • sizeof
  • stackalloc
  • string
  • struct
  • switch
  • this
  • throw
  • true
  • try
  • typeof
  • uint
  • ulong
  • unchecked
  • ushort
  • using
  • var
  • void
  • while
  • public
  • private
  • internal
  • protected
  • abstract
  • async
  • const
  • event
  • extern
  • new
  • override
  • partial
  • readonly
  • sealed
  • static
  • unsafe
  • virtual
  • volatile
Una vez que tenemos la lista, podemos crear una expresión regular teniendo en cuenta que son palabras exactas entonces si tomamos de la lista anterior solo los primeros tres elemento, la expresion regular queda asi

\b(as|base|bool)\b

Obviamente hay que agregar el resto de ellas para poder encontrar todas.

Para poder encontrar los números, primero tenemos que recordar que en C# se pueden escribir los numero directamente en base decimal o en base hexadecimal. Para la base hexadecimal hay que recordar que los número tendrán el formato "0x" antes de cualquier número en esa base.

Para poder encontrar los números, podemos crear una expresión regular cómo esta:

(\b(\d+)\b)

para los números decimales. Y algo cómo esta:

(0[xX])(\d|[a-f|A-F])+)

Para los números hexadecimales.

Para poder encontrar los comentarios, lo primero que haremos es recordar cómo es que forman los comentarios en c#. En C# hay al menos dos tipos de comentarios. El primero es el comentario sencillo y siempre comienza por "//" hasta el final de la linea. El segundo es el comentario multi-linea, este consiste que se comentará todo desde el inicio hasta al final (aunque parezca redundante), eso quiere decir que desde la marca de inicio "/*" sin importar que haya (nuevas lineas, apostrofes, comas, etc.) el comentario se hará hasta que se encuentre con el indicio de cierre "*/"

Para encontrar los comentarios simple podemos crear una expresión regular cómo la que sigue:

(\/\/.+)

Y para los comentario de muchas líneas, podemos crear una cómo la que sigue

(\/\*[\s\S]*?\*\/))

Para las cadenas texto hay que recordar que siempre van entre comillas " " ":

(\".*?\")

Y para los operadores en C#, la lista es la siguiente:

  • []
  • ()
  • .
  • ?
  • and
  • ::
  • +
  • -
  • *
  • /
  • %
  • &
  • ^
  • !
  • ~
  • =
  • <
  • >
  • ?:
  • ++
  • --
  • &&
  • <<
  • >>
  • ==
  • !=
  • <=
  • >=
  • +=
  • -=
  • *=
  • /=
  • %=
  • &=
  • =
  • ^=
  • <<=
  • >>=
  • ->
  • ??
  • =>

Y podemos crear un patrón cómo el que sigue (Solo para los primeros tres).

(\[|\]|\()

Ahora podemos pasar al código en C#.

/*+----------------------------------------------------------+
| Lista de palabras reservadas                               |
+------------------------------------------------------------+
| as|base|bool|break|byte|case|catch|char|checked|class      |
+------------------------------------------------------------+
| continue|decimal|default|delegate|do|double|else|enum      |
+------------------------------------------------------------+
| explicit|false|finally|fixed|float|for|foreach|goto|if     |
+------------------------------------------------------------+
| implicit|in|int|interface|is|lock|long|namespace|null      |
+------------------------------------------------------------+
| object|operator|out|params|ref|return|sbyte|short|sizeof   |
+------------------------------------------------------------+
| stackalloc|string|struct|switch|this|throw|true|try|typeof |
+------------------------------------------------------------+
| uint|ulong|unchecked|ushort|using|var|void|while           |
+----------------------------------------------------------+*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Text.RegularExpressions;

namespace SyntaxHighlighter
{
	public partial class FrmMain : Form
	{
		private string ReservedWords1 = @"\b(as|base|bool|break|byte|case|catch|char|checked|class|continue|decimal|default|delegate|do|double|else|enum|explicit|false|finally|fixed|float|for|foreach|goto|if|implicit|in|int|interface|is|lock|long|namespace|null|object|operator|out|params|ref|return|sbyte|short|sizeof|stackalloc|string|struct|switch|this|throw|true|try|typeof|uint|ulong|unchecked|ushort|using|var|void|while)\b";
		private string ReservedWords2 = @"\b(public|private|internal|protected|abstract|async|const|event|extern|new|override|partial|readonly|sealed|static|unsafe|virtual|volatile)\b";
		private string Comments = @"((\/\/.+)|(\/\*[\s\S]*?\*\/))";
		private string Numbers = @"((0[xX])(\d|[a-f|A-F])+)|(\b(\d+)\b)";
		private string Strings = "(\".*?\")";
		private string Operators = @"\[|\]|\(|\)|\.|\?|(and)|(\:\:)|\+|\-|\*|\/|\%|\&|\^|\!|\~|\=|\<|\>|\?\:|\++\|\-\-|\&\&|\<\<|\>\>|\=\=|\!\=|\<\=|\>\=|\+\=|\-\=|\*\=|\/\=|\%\=|\&\=|\=|\^\=|\<\<\=|\>\>\=|\-\>|\?\?|\=\>|";
		public FrmMain()
		{
			InitializeComponent();
		}

		private Color FontColor1 = (Color)Properties.Settings.Default["ColorRW1"];
		private Color FontColor2 = (Color)Properties.Settings.Default["ColorRW2"];
		//private Color FontColor3 = (Color)Properties.Settings.Default["ColorRW3"];
		private Color FontComments = (Color)Properties.Settings.Default["ColorCom"];
		private Color FontNum = (Color)Properties.Settings.Default["ColorNum"];
		private Color FontStrings = (Color)Properties.Settings.Default["ColorStrings"];
		private Color FontOperator = (Color)Properties.Settings.Default["ColorOp"];

		private enum WordTypes
		{
			WType1  = 0,
			WType2,
			WType3,
			CType,
			NType,
			SType,
			OpType,
			OtherType
		}

		private void ColorizeAll()
		{
			Colorize(ReservedWords1, WordTypes.WType1);
			Colorize(ReservedWords2, WordTypes.WType2);
			Colorize(Numbers, WordTypes.NType);
			Colorize(Operators, WordTypes.OpType);
			Colorize(Strings, WordTypes.SType);
			Colorize(Comments, WordTypes.CType);
		}

		private void Colorize(string Patron, WordTypes Tipo)
		{
			Color CurrentFontColor = Color.Black;
			switch (Tipo)
			{
				case WordTypes.WType1:
				CurrentFontColor = FontColor1;
				break;
				case WordTypes.WType2:
				 CurrentFontColor = FontColor2;
				break;
				case WordTypes.WType3:
				CurrentFontColor = Color.Black;
				break;
				case WordTypes.CType:
				CurrentFontColor = FontComments;
				break;
				case WordTypes.NType:
				CurrentFontColor = FontNum;
				break;
				case WordTypes.SType:
				CurrentFontColor = FontStrings;
				break;
				case WordTypes.OpType:
				CurrentFontColor = FontOperator;
				break;
				case WordTypes.OtherType:
				CurrentFontColor = Color.Black;
				 break;
			}

				MatchCollection wordColl = Regex.Matches(RTBMain.Text, Patron);
				foreach (Match m in wordColl)
				{
					RTBMain.SelectionStart = m.Index;
					RTBMain.SelectionLength = m.Length;
					RTBMain.SelectionColor = CurrentFontColor;
			}
		}

		private void TSBtnOpen_Click(object sender, EventArgs e)
		{
			OpenFileDialog OpDiag = new OpenFileDialog();
			OpDiag.Filter = "Código fuente C#|*.cs|Archivos de texto|*.txt|Todos los archivos|*.*";
			OpDiag.Multiselect = false;
			DialogResult DiagRes = OpDiag.ShowDialog();
			if (DiagRes == DialogResult.OK)
			{
				RTBMain.Clear();
				StreamReader SR = new StreamReader(OpDiag.FileName, Encoding.UTF8);
				RTBMain.AppendText(SR.ReadToEnd());
				ColorizeAll();
				
			}
		}

		private void TSBtnSave_Click(object sender, EventArgs e)
		{
			SaveFileDialog SaveDiag = new SaveFileDialog();
			SaveDiag.Filter = "Texto enriquecido|*.rtf";
			if (SaveDiag.ShowDialog() == DialogResult.OK)
			RTBMain.SaveFile(SaveDiag.FileName);
		}

		private void TSBtnFont_Click(object sender, EventArgs e)
		{
			FontDialog FontDiag = new FontDialog();
			FontDiag.ShowApply = false;
			if(FontDiag.ShowDialog() == DialogResult.OK)
				RTBMain.Font = FontDiag.Font;
		}

		private void TSBtnColor_Click(object sender, EventArgs e)
		{
			Form FrmOptions = new FrmAjustes();
			DialogResult DiagRes = FrmOptions.ShowDialog();
			if (DiagRes == DialogResult.OK)
			{
			}
		}
	}
}

Y bien, por ahora es todo, en lo mientras y para no alargar tanto el post, puedes descargar la versión beta del programas desde mi dropbox, la semana que viene, explicare cada parte del código (por ahora no, ya que mas seguro buena parte va a cambiar) y agrgare más funciones para que no solo resalte el código de c#

Los leo luego

No hay comentarios.