Autómatas celulares, Aplicación WinCA VI
Seguimos con la serie dedicada a los autómatas celulares y la aplicación WinCA para construirlos y ejecutarlos. En este artículo voy a explicar el código relacionado con el sistema de expresiones que permite establecer las condiciones para cambiar de un estado a otro.
Aquí puedes encontrar el primer artículo de la serie sobre la aplicación WinCA.
En este enlace puedes descargar los ejecutables de la aplicación WinCA, y en este otro puedes descargar el código fuente de la solución WinCA, escrita en CSharp con Visual Studio 2015.
Compilador del lenguaje
Las expresiones del lenguaje se compilan utilizando el compilador de la librería de clases BNFUP, del que ya os he hablado en el artículo sobre el compilador universal de objetos BNFUP. Esta es la sintaxis en forma de reglas BNF:
<<boolexpr>>::=<boolexpr1>['|'<boolexpr>];
<boolexpr1>::=<boolexpr0>['&'<boolexpr1>];
<boolexpr0>::=['!']<boolelement>;
<boolelement>::=<pboolexpr>
|<kbool>
|<truthfunction>
|<boolstrfunction>
|<boolaggregate>
|<relexpr>;
<pboolexpr>::='{'<boolexpr>'}';
<kbool>::='$t','$f'
<truthfunction>::='ist(','isf('<valarg>')';
<boolaggregate>::='and(','or(' <boolexpr> ')';
<boolstrfunction>::='cont(','stw(','endw(','match(',
'streq(','streqcs(' <strexpr>','<strexpr>')';
<valarg>::=<valref>
|<propref>
|<indexedprop>
|<iteratorprop>;
<relexpr>::=<expr>'<<','<=','==','>=','>>','<>'<expr>;
<expr>::=<expr2>['+','-','@','#'<expr>];
<expr2>::=<expr1>['*','/','%','?'<expr2>];
<expr1>::=<expr0>['^'<expr1>];
<expr0>::=['-']<element>;
<element>::=<pexpr>
|<number>
|<kvalue>
|<valarg>
|<intfunction>
|<intaggregate>
|<intstrfunction>
|<dblfunction>
|<dblaggregate>;
<pexpr>::='('<expr>')';
<number>::=<digit>[<rnumber>]
|'.'<rdecimal>;
<rnumber>::=<digit>[<rnumber>]
|'.'<rdecimal>;
<rdecimal>::=<digit>[<rdecimal>];
<digit>::={0-9};
<idvalue>::=<bslash><letter>[<ridentifier>];
<idprop>::=<bbar><letter>[<ridentifier>];
<ridentifier>::=<validchar>[<ridentifier>];
<bslash>::={_};
<bbar>::={\\};
<letter>::={A-Za-z};
<validchar>::={_A-Za-z0-9};
<typedef>::='int','double','bool','string';
<valref>::=<idvalue>[':' <typedef>];
<propref>::= <idprop>[':' <typedef>];
<indexedprop>::='['<expr>']' '.' <propref>;
<iteratorprop>::=';'<propref>;
<kvalue>::='$e','$pi','$runi','$rnorm';
<strchars>::={^"}[<strchars>]
|'\"'[<strchars>];
<string>::='"'[<strchars>]'"';
<intaggregate>::=<icountfunction>
|<iaggfunction>;
<icountfunction>::='icount(' <boolexpr> ')';
<iaggfunction>::='isum(','iprod(','imax(','imin(','iavg(' <expr> ')';
<intfunction>::='int(','isign(','ifloor(','iceil(','iabs(',
'red(','green(','blue(' <expr> ')';
<intstrfunction>::='cmp(','pos('<strexpr>','<strexpr>')';
<dblaggregate>::=<dcountfunction>
|<daggfunction>;
<dcountfunction>::='dcount(' <boolexpr> ')';
<daggfunction>::='dsum(','dprod(','dmax(','dmin(','var(',
'sd(','svar(','ssd(','davg(' <expr> ')';
<dblfunction>::='double(','dsign(','ln(','sqrt(','exp(',
'log(','log2(','dfloor(','dceil(','sin(','cos(','tan(','sinh(',
'cosh(','tanh(','dabs(','hue(','sat(','bri(' <expr> ')';
<strexpr>::=<string>
|<strsubs>
|<strstr>
|<strrepl>
|<valarg>;
<strsubs>::='subs('<strexpr>','<expr>','<expr>')';
<strstr>::='strc('<strexpr>','<strexpr>')';
<strrepl>::='repl('<strexpr>','<strexpr>','<strexpr>')';
El archivo con las reglas compiladas es expressions.bnf, que se encuentra incrustado en el archivo de recursos Resources.resx de la librería de clases CellularAutomata. Este archivo se puede modificar usando la aplicación BNFUPEditor, de la solución BNFUP, que podéis encontrar en el enlace anterior.
En este artículo sobre la sintaxis del lenguaje de WinCA podéis encontrar una explicación sobre el uso de los diferentes elementos que componen estas expresiones.
El generador de las expresiones se encuentra implementado en la clase ExpressionFactory, en el espacio de nombres Expressions de la librería de clases CellularAutomata.
Implementación de las expresiones
Además de los interfaces propios del compilador BNFUP, los elementos de las expresiones de WinCA implementan el interfaz IExpressionValue, definido en el espacio de nombres Interfaces de la librería de clases CommonObjects, de la siguiente manera:
public interface IExpressionValue
{
IValueProvider ValueProvider { get; set; }
int AsInt { get; }
double AsDouble { get; }
string AsString { get; }
bool AsBool { get; }
Color AsColor { get; }
ExprType PreferredType { get; }
}
ValueProvider obtiene o establece el objeto que permite acceder a los valores de las propiedades de las celdas del autómata, normalmente es de la clase que implementa el propio autómata. Con las propiedades AsInt, AsDouble, AsString, AsBool y AsColor se puede obtener el valor de la expresión con diferentes tipos de datos, y PreferredType es el tipo de datos nativo de la expresión.
El interfaz IValueProvider, en la misma librería de clases y espacio de nombres, está definido de la siguiente manera:
public interface IValueProvider
{
IVariantValue GetVarValue(string name);
IVariantValue GetPropertyValue(string name);
IVariantValue GetIteratorPropertyValue(string name);
IVariantValue GetIndexedPropertyValue(int ix, string name);
void BeginIterator();
bool Next();
void EndIterator();
}
El método GetVarValue devuelve el valor de una variable identificada por su nombre, mientras que GetPropertyValue hace lo mismo con el valor de la propiedad con nombre name del estado de la celda actual del autómata.
Con GetIndexedPropertyValue, podemos obtener el valor de la propiedad con nombre name del estado de la celda vecina de la actual con el índice ix.
Para iterar a través de todas las celdas vecinas de la celda actual, se debe iniciar un bucle con el método BeginIterator, obtener el valor de la propiedad con GetIteratorPropertyValue, pasando el nombre de la propiedad en el parámetro name, y continuar con el método Next para pasar a la siguiente celda vecina, devolverá true si existen más vecinos. El método EndIterator debe ser llamado cuando haya terminado el bucle.
Las clases que implementan los diferentes elementos de las expresiones se encuentran en el espacio de nombres Expressions de la librería de clases CommonObjects. Todas derivan de la clase abstracta ExprBase. Esta clase implementa el interfaz ICompilableObject, necesario para el compilador BNFUP, e IExpressionValue, además de las siguientes propiedades y métodos:
public bool Parenthesis { get; set; }
public virtual bool Indexed { get; }
public virtual string AsText { get; }
public ITestProvider TestProvider { get; set; }
public virtual int OperandCount { get; }
public virtual ExprBase Operand(int n);
- Parenthesis: Se utiliza para alterar la precedencia de operadores. La subexpresión y sus componentes se consideran como un elemento simple a la hora de simplificar la expresión de la que forma parte.
- Indexed: Devuelve true si en la expresión existe algún operando que hace referencia a alguna de las celdas vecinas por su índice.
- AsText: Devuelve una versión del objeto en forma de cadena de texto, para su representación en el interfaz de usuario.
- TestProvider: Mediante esta propiedad se proporciona al objeto acceso a un formulario que implementa la posibilidad de probar una expresión para su depuración. Actualmente no se utiliza esta propiedad.
- OperandCount: Devuelve el número de operandos de la expresión.
- Operand: Devuelve el enésimo operando de la expresión.
Para implementar los operadores de las expresiones, existe la clase base Operator, que solamente implementa el interfaz ICompilableObject. Además, ofrece las propiedades y métodos siguientes:
public virtual int Precedence { get; }
public virtual double Operate(double v);
public virtual int Operate(int v);
public virtual bool Operate(bool v);
public virtual double Operate(double v1, double v2);
public virtual int Operate(int v1, int v2);
public virtual bool Operate(bool v1, bool v2);
Precedence devuelve un valor que representa la precedencia del operador. Se utiliza a la hora de simplificar y reagrupar las subexpresiones. Las diferentes versiones de Operate permiten realizar la operación que representa sobre uno o dos argumentos, usando diferentes combinaciones de tipos de datos.
De esta clase base derivan los tres tipos de operadores que utiliza el lenguaje, BoolOperator, para operaciones lógicas, ArithmeticOperator, para operaciones aritméticas y RelOperator para operaciones relacionales.
Existen tres clases de expresiones en el lenguaje, expresiones lógicas, aritméticas y relacionales. Además, existen funciones que utilizan argumentos de estos tres tipos, además de cadenas de texto. Una expresión aritmética no puede utilizar argumentos de tipo booleano, por ejemplo, y en las expresiones lógicas los argumentos no pueden ser números. Para diferenciar los diferentes tipos de argumentos, existen las clases base abstractas, derivadas de ExprBase, UniversalArgument, que se utiliza para argumentos que se pueden usar en cualquier expresión, como por ejemplo los que representan el valor de una propiedad, ArithmeticArgument, para argumentos de tipo numérico, BoolArgument, para argumentos de tipo booleano, y StringArgument, para argumentos que son cadenas de texto.
Todos los operandos derivan de alguna de estas clases, de manera que, en tiempo de compilación, se puede comprobar que sus tipos son apropiados para la expresión que se está construyendo.
Los diferentes tipos de expresiones se implementan en las clases LogicExpression, ArithmeticExpression y RelExpression.
Los componentes más simples son las constantes numéricas, implementadas en la clase Number, las constantes de cadena de texto, en la clase StringLiteral, las constantes lógicas, en la clase BoolValue, las constantes y pseudoconstantes aritméticas, como el número pi o los generadores de valores aleatorios, en la clase ArithmeticValue, y las referencias a variables en la clase VariableReference.
Para acceder a las propiedades de los estados, con las diferentes modalidades de acceso, existen las clases PropertyReference, que permite acceder a las propiedades del estado de la celda actual, IndexedProperty, para acceder a las propiedades de las celdas vecinas por su índice, e IteratorPropertry, para acceder a todas las celdas vecinas en un proceso de iteración.
La clase TypeDef implementa las funciones para forzar un cambio de tipo. El resto de funciones se implementa en las clases IntFunction, para las funciones que trabajan con enteros, DoubleFunction, para funciones que trabajan con valores double, StrBoolFunction, para funciones con argumentos de tipo string y resultado de tipo bool, StrIntFunction¸ para funciones con argumentos de tipo string que devuelven enteros, y funciones específicas que trabajan con cadenas de texto en las clases StrSubstringFunction, StrConcatFunction y StrReplaceFunction. Por último, en la clase TruthFunction se implementan las dos funciones que comprueban si el argumento es verdadero o falso.
Las funciones de agregación, que realizan los cálculos sobre todas las celdas vecinas de la actual, se implementan en las clases BoolAggregate, para funciones lógicas, IntCountAggregate y DoubleCountAggregate, derivadas de CountAggregate, que implementan las funciones de recuento, DoubleAggregateFunction, para los agregados que trabajan con valores double, e IntAggregateFunction, para los agregados que trabajan con valores enteros.
Por último, existen algunas clases auxiliares que solo se utilizan en tiempo de compilación, como FunctionName, Identifier, VarIdentifier y PropIdentifier, con las que se construyen los identificadores de funciones, variables y propiedades para pasárselos a los objetos en construcción.
Formularios para editar las expresiones
En la aplicación WinCA, el formulario que se encarga de editar las expresiones de cambio de estado es frmTransitions, en el espacio de nombres Forms.
En este formulario se pueden compilar las expresiones para comprobar la sintaxis. El formulario que presenta los mensajes de compilación es frmMessages, del espacio de nombres Utils. Para probar las expresiones se utiliza el formulario frmTest, en el espacio de nombres Expressions de la librería de clases CellularAutomata.
Y eso es todo en cuanto a las expresiones, en el siguiente artículo mostraré los interfaces y clases relacionados con las celdas de los autómatas.