Esta es una imagen de como luce la interfaz grafica.
Este proyecto se ha divido en dos partes principales, una libreria de clases llamada THE_HULK
que es la encargada de almacenar todo el motor de procesamiento y una aplicacion de consola llamada BRUNCE_BANNER
Interfaz grafica.
Libreria de clases.
HULK es un lenguaje de programación imperativo, funcional, estática y fuertemente tipado. Casi todas las instrucciones en HULK son expresiones.En particular, el subconjunto de HULK a implementar se compone solamente de expresiones que pueden escribirse en una línea.
Todas las instrucciones en HULK terminan en ;
Entrando en la libreria de clases podemos ver que se divide en dos partes fundamentales:
-Lexer -Parser
A lo largo de esta exposicion entraremos en detalles en cada uno de ellos.
Dentro de la clase Lexer
hay una pequenna descripcion de como funciona un Lexer.
A Lexer, short for lexical analyzer, is a fundamental component of a compiler or interpreter. Its main purpose is to break down the source code into smaller units called tokens. Tokens are the building blocks of a programming language and represent meaningful elements such as keywords, identifiers, operators, literals, and punctuation. The Lexer scans the source code character by character and groups them into tokens based on predefined rules and patterns. It identifies the lexical structure of the code, ignoring whitespace and comments, and produces a stream of tokens that can be further processed by the parser. In the provided code excerpt, the Lexer class represents the implementation of a Lexer. It takes the source code as input and provides a Tokenize method to tokenize the code. The Tokenize method uses a loop to iterate over each character of the source code and identifies the current character as a token.
Basicamente a modo de resumen el Lexer
se encarga de procesar el codigo ingresado por el usuario y convertirlo en tokens, los cuales mas adelantes seran procesados por el Parser
para dar un resultado.
El metodo de Lexing utilizado en THE_HULK es un metodo del tipo LL(1)
[L]ef to Right - [L]eftmost - [N]umber of operations. Basado en ir analizando caracter a caracter el input del usuario mirando siempre el caracter siguiente.
public class Lexer
{
public string imput;
private int currentPos;
public Lexer(string input)
{
this.imput = input;
currentPos = 0;
}
}
El lexer solo contiene dos propiedades esenciales, input
utilizada para procesar el input realizado por el usuario y currentPos
para navegar a travez de los caracteres del input.
Luego, la clase contiene un metodo principal Tokenizer
, encargado de devolver una lista de tokens. Analicemos dicho metodo:
public List<Token> Tokenizer()
{
// List of tokens
List<Token> tokens = new();
while (currentPos < imput.Length)
{
// Current character
char currentChar = imput[currentPos];
// Ignore whitespace
if (char.IsWhiteSpace(currentChar))
{
currentPos++;
continue;
}
// Add identifier or keyword
else if (IsLetter(currentChar))
{
tokens.Add(UnkwnonType());
}
// Add string
else if (currentChar == '"')
{
tokens.Add(String());
}
// Add number
else if (char.IsDigit(currentChar))
{
tokens.Add(Number());
}
// Add operator
else if (IsOperator(currentChar))
{
tokens.Add(Operator());
}
// Add punctuator mark
else if (IsMark(currentChar))
{
tokens.Add(Puntuation_Marks());
}
// Add unknown token
else
{
tokens.Add(new TokenCommon(TokenKind.Unknown, currentChar.ToString()));
System.Console.WriteLine($"! LEXICAL ERROR: \"{tokens.Last()}\" isn't a valid token.");
currentPos++;
}
}
// Add EOF token
if (tokens.Last().GetTokenName() != ";")
{
System.Console.WriteLine("! SYNTAX ERROR: expression must end with \";\".");
throw new Exception();
}
return tokens;
}
Para procesar la manera de tokenizar separamos en casos:
-
Si lo que encontramos es un espacio en blanco, continuamos. (currenPos++)
-
Si el caracter sobre el que estamos es una letra llamamos a la funcion
UnkwnonType
que se encargara de procesarlo:
private Token UnkwnonType()
{
string unkwnon = "";
while (currentPos < imput.Length && (IsLetterOrDigit(imput[currentPos]) || imput[currentPos] == '_'))
{
unkwnon += imput[currentPos];
currentPos++;
}
if (IsKeyWord(unkwnon))
{
return KeyWord(unkwnon);
}
else
{
return new TokenCommon(TokenKind.Identifier, unkwnon);
}
}
Leemos caracter a caracter mientras sea digito, letra o
_
. Cuando el caracter deje de ser uno de los anteriores paramos para comprobar si es keyword, en caso de serlo se annade a los tokens como Keyword, en caso de no serlo se annade como una variable.
- Si el caracter en que estamos es
"
pasamos a procesar un string con la funcionString
.
private Token String()
{
currentPos++;
string temporal = "";
while (currentPos < imput.Length && imput[currentPos] != '"')
{
temporal += imput[currentPos];
currentPos++;
}
Next();
return new TokenData(TokenKind.String, temporal);
}
Se annade todo el texto entre comillas y se devuelve como un token.
- Si el caracter encontrado es un numero pasamos a procesarlo con la funcion
Number
private Token Number()
{
string number = "";
while ((currentPos < imput.Length) && (char.IsDigit(imput[currentPos]) || imput[currentPos] == '.'))
{
number += imput[currentPos];
if (IsLetter(LookAhead(1)))
{
Console.WriteLine($"! LEXICAL ERROR: \"{number + LookAhead(1)}\" isn't a valid token.");
throw new Exception();
}
currentPos++;
}
return new TokenData(TokenKind.Number, Double.Parse(number));
}
Siempre se verifica que el numero no contenga algo distinto a digito numero o a un
.
, en caso de suceder se lanza una exepcion.
- Si el caracter es un operador pasamos a tokenizarlo.
private Token Operator()
{
char _operator = imput[currentPos];
switch (_operator)
{
case '+':
Next();
return new TokenCommon(TokenKind.Sum, _operator.ToString());
...
Se implementa un
switch case
con los tipos de operadores prosibles y se reconoce.
- Posteriormente pasamos al ultimo de los casos que es reconocer un simbolo de puntuacion:
private Token Puntuation_Marks()
{
char punctuator = imput[currentPos];
switch (punctuator)
{
case '"':
Next();
return new TokenCommon(TokenKind.Quote, punctuator.ToString());
...
Se procesa de manera omologa al paso anterior.
-
Si entramos en este if podemos afirmar que el usuario ha escrito un input no admitido por el leguage Hulk y pasamos a lanzar una exepcion.
-
Para finalizar y comprobar que se haya terminado la instruccion se comprueba que el ultimo de los tokens sea
;
el EOF, en caso de no serlo se lanza una exepcion.
Primero implementamos una clase abstracta que nos servira como plantilla para procesar de manera efectiva cada token.
public abstract class Token
{
public TokenKind Kind { get; set; }
public Token(TokenKind kind)
{
Kind = kind;
}
public TokenKind GetTokenKind() => Kind;
public abstract string GetTokenName();
public abstract object GetTokenValue();
public override string ToString() => $"{Kind}";
}
Como se puede obervar la clase Token contiene la propiedad Kind
del tipo TokenKind
:
Para la identificacion del tipado de un token utilizamos un enum
que contenga todos los tipos posibles de tokens, haciendo mas facil el reconocimiento de un token.
public enum TokenKind
{
Identifier, // Variables
// ===>>> KeyWords <<<===
IfKeyWord, // if
InKeyWord, // in
LetKeyWord, // let
ElseKeyWord, // else
FunctionKeyWord, // function
// ===>>> End of KeyWords <<<===
// ===>>> Data Types <<<===
String, // "string"
Number, // 1.0
TrueKeyWord, // true
FalseKeyWord, // false
// ===>>> End of Data Types <<<===
// ===>>> Numeric Operators <<<===
Sum, // +
Power, // ^
Modulo, // %
Product, // *
Quotient, // /
Difference, // -
// ===>>> End of Numeric Operators <<<===
// ===>>> Logical Operators <<<===
Or, // ||
Not, // !
And, // &&
UnEqual, // !=
EqualTo, // ==
LessThan, // <
EqualEqual, // ==
GreaterThan, // >
Concatenation, // @
LessOrEqualThan, // <=
GreaterOrEqualThan, // >=
// ===>>> End of Logical Operators <<<===
// ===>>> Symbols <<<===
End, // ;
Arrow, // >
Quote, // "
Comma, // ,
Colon, // :
Semicolon, // ;
LeftParenthesis, // (
RightParenthesis, // )
// ===>>> End of Symbols <<<===
// ===>>> Comments <<<===
EndOfFile, // End of File \n
Unknown, // Unknown
// ===>>> End of Comments <<<===
}
Luego pasamos a entrar en los tres tipos de tokens basicos que podemos encontrar en Hulk.
(Se pudo habler implementado una unica clase generica para la siguiente tarea)
public class TokenCommon : Token
{
public string symbol { get; set; }
public TokenCommon(TokenKind kind, string _symbol) : base(kind)
{
symbol = _symbol;
}
public override string GetTokenName() => symbol;
//This tokens has no value.
public override object GetTokenValue() => throw new NotImplementedException();
public override string ToString() => $"{base.Kind}: {symbol}";
}
Dentro de esta clase estan comprendidos principalmente los operadores y simbolos de puntuacion.
public class TokenData : Token
{
public object value { get; set; }
public TokenData(TokenKind kind, object _value) : base(kind)
{
this.value = _value;
}
public override string GetTokenName() => value.ToString()!;
public override object GetTokenValue() => value;
public override string ToString() => $"{base.Kind}: {value}";
}
Procesamos los principales elementos soportados por el lenguaje:
number
,string
,bool
y las variables declaradas por el usuario.
public class TokenKeyword : Token
{
public TokenKeyword(TokenKind kind) : base(kind)
{
Kind = kind;
}
public override string GetTokenName() => Kind.ToString();
public override object GetTokenValue() => throw new NotImplementedException();
}
Esta clase se utiliza para procesar las
keywords
del lenguaje Hulk.
El parser como mencionado anteriormente es el encargado de procesar los token para devolver un resultado. Para parsear el input del usuario necesitaremos construir un arbol de sintaxis abstracta y resolverlo, pero antes de construirlo necesitaremos contruir antes las estructuras necesarias. Comencemos implementando las expresiones.
public abstract class Expression
{
public abstract ExpressionKind Kind { get; set; }
public abstract object? value { get; set; }
public virtual Environment? environment { get; set; }
public Expression(Environment _environment)
{
environment = _environment;
}
public virtual void Evaluate(Environment _environment) { return; }
public override string ToString() => $"{value}";
public abstract object? GetValue();
}
Anteriormente mencionamos que casi todo en Hulk es una expresion, dado el caso al igual que en los tokens implementaremos una plantilla basica para todo tipo de expresion.
Igualmente que como vimos en token la clase
Expression
cuenta con la propiedadKind
del tipoExpressionKind
.
public enum ExpressionKind
{
Number, // Doubles
Bool, // Booleans
String, // Strings
Temp, // Variables
}
A continuacion pasamos a implementar las plantillas para los tipos de expresiones que mas usaremos.
BasicExpression
public abstract class BasicExpression : Expression
{
public override Environment? environment { get; set; }
public BasicExpression(Environment _environment) : base(_environment) { }
public override string ToString() => $"{value}";
}
Esta sera la plantilla de las expresiones mas basicas del lenguaje, como lo son los string, bools, numeros, o expresiones como log, sin, cos, etc.
Estas expresiones no contienen nodos puesto que ellas son los nodos en si.
UnaryExpression
public abstract class UnaryExpression : Expression
{
public abstract Expression node { get; set; }
public UnaryExpression(Expression _node) : base(null!)
{
node = _node;
}
public abstract void SemantiCheck(Expression _node);
}
Esta sera la plantilla encargada de ser implementada por las operaciones unarias del lenguaje, como lo es la negacion.
Este tipo de operaciones contiene un solo nodo, puesto que son unarias.
Toda clase que herede de esta clase tendra que implementar la funcion
SemantiCheck
para asegurarse que la expresion este bien compuesta semanticamente.
BinaryExpression
public abstract class BinaryExpression : Expression
{
public Expression nodeLeft;
public Expression nodeRight;
public TokenKind Operator;
public override Environment? environment { get; set; }
public BinaryExpression(TokenKind _operator, Expression _nodeLeft, Expression _nodeRight) : base(null!)
{
nodeLeft = _nodeLeft;
nodeRight = _nodeRight;
Operator = _operator;
}
public virtual void SemantiCheck(Expression nodeLeft, TokenKind operator_, Expression nodeRight)
{
if ((nodeLeft!.Kind != ExpressionKind.Number && nodeLeft.Kind != ExpressionKind.Temp) || (nodeRight!.Kind != ExpressionKind.Number && nodeRight.Kind != ExpressionKind.Temp))
{
System.Console.WriteLine($"! SEMANTIC ERROR: \"{operator_}\" cannot be used between \"{nodeLeft.Kind}\" and \"{nodeRight!.Kind}\".");
throw new Exception();
}
}
}
Esta plantilla funciona de manera omologa a
UnaryExpression
lo que contiene un nodo extra y el chequeo de semantica se verifica sobre que los dos nodos sean del mismo tipado.
Entrando en las expresiones mas basicas analizaremos print
pero el resto de las clases se comportan de forma semejante
public class Print : BasicExpression
{
public override ExpressionKind Kind { get; set; }
public override object? value { get; set; }
List<Expression> parameters;
public Print(List<Expression> _parameters, Environment _environment) : base(_environment)
{
this.parameters = _parameters;
Kind = ExpressionKind.Temp;
if (_parameters.Count != 1)
{
System.Console.WriteLine($"! SEMANTIC ERROR: function \"cos\" needs 1 parameter(s), but {_parameters.Count} were given.");
throw new Exception();
}
}
public override void Evaluate(Environment _environment)
{
parameters.First().Evaluate(_environment);
value = parameters.First().value;
System.Console.WriteLine(value);
}
public override object? GetValue() => value;
}
Todas las expresiones basicas tienen un paramentro
paramenter
oparameters
porque expresiones mas simples como cos, sin, reciben un solo parametro.
Luego tenemos un chequeo semantico para comprobar que que la cantidad de parametros con los que se llamo sean los mismo que necesita la expresion
Un ejemplo basico de lo anterior es que la funcion
Logarithm
necesita exactamente dos parametros para ejecutarse de manera efectiva, luego si se llama con una cantidad distinta a 2 podemos afirmar que existe un error semantico.
Luego implementa el metodo
Evaluate
que en el caso dado es imprimir el resultado de la(s) expresion(es) dada(s).
A continuacion ejemplos de las funciones
Ejemplo del metodo
Sin
.
Ejemplo del metodo
Cos
.
Ejemplo del metodo
Logarithm
.
Ejemplo del metodo
Exponential
.
Ejemplo del metodo
SquareRoot
.
Entrando en las expresiones unarias podemos analizar Not
para entender su funcionamineto.
public class Not : UnaryExpression
{
public override Expression node { get; set; }
public override ExpressionKind Kind { get; set; }
public override object? value { get; set; }
public override Environment? environment { get; set; }
public Not(Expression _node) : base(_node)
{
Kind = ExpressionKind.Bool;
this.node = _node;
}
public override void SemantiCheck(Expression node)
{
if (this.node.Kind != ExpressionKind.Bool && this.node.Kind != ExpressionKind.Temp)
{
System.Console.WriteLine($"! SEMANTIC ERROR: operator \"{TokenKind.Not}\" cannot be applied to \"{this.node.Kind}\".");
throw new Exception();
}
}
public override void Evaluate(Environment _environment)
{
node.Evaluate(environment!);
value = !(bool)node.GetValue()!;
}
public override object? GetValue() => value;
}
En esta clase se realiza un chequeo semantico para comprobar que sea un elemento booleano sobre el cual se realizara la operacion de negacion, en caso de no serlo lanzamos una exepcion.
De no haber errores semanticos procedemos a evaluar el cual solo invertira el valor sobre el cual fue operado.
Ejemplo de negacion
En el caso las operaciones binarias existen 3 tipos en las que las podemos dividir. Miremos cada una de ellas.
Adentrandonos en las expresiones booleanas tomaremos como ejemplo principal GreaterOrEqualThan
, el resto de las operaciones booleanas se comportan de manera semejante.
public class GreaterOrEqualThan : BinaryExpression
{
public GreaterOrEqualThan(TokenKind _operator, Expression nodeLeft, Expression nodeRight) :
base(_operator, nodeLeft, nodeRight)
{
Kind = ExpressionKind.Bool;
}
public override ExpressionKind Kind { get; set; }
public override object? value { get; set; }
public override void Evaluate(Environment _environment)
{
nodeLeft.Evaluate(_environment);
nodeRight.Evaluate(_environment);
if (nodeLeft.Kind != ExpressionKind.Number || nodeRight.Kind != ExpressionKind.Number)
{
System.Console.WriteLine($"! SEMANTIC ERROR: \"{Operator}\" cannot be used between \"{nodeLeft.Kind}\" and \"{nodeRight.Kind}\".");
throw new Exception();
}
value = (double)nodeLeft.GetValue()! >= (double)nodeRight.GetValue()!;
}
public override object? GetValue() => value;
}
Esta clase cuenta con un metodo principal que se encarga de evaluar la accion, en caso del usuario estar intentando comparar elementos que no sean numeros con este operador se procede a lanzar una exepcion.
Ejemplo del metodo
And
.
Ejemplo del metodo
Or
.
Ejemplo del metodo
GreaterThan
.
Ejemplo del metodo
LessThan
.
Ejemplo del metodo
GreaterOrEqualThan
.
Ejemplo del metodo
LessOrEqualThan
.
Ejemplo del metodo
EqualTo
.
En el caso de las expresiones numericas tomaremos como ejemplo principal Sum
, el resto de las operaciones numericas se comportan de manera semejante.
public class Sum : BinaryExpression
{
public override ExpressionKind Kind { get; set; }
public override object? value { get; set; }
public Sum(TokenKind _operator, Expression nodeLeft, Expression nodeRight) :
base(_operator, nodeLeft, nodeRight)
{
Kind = ExpressionKind.Number;
}
public override void Evaluate(Environment _environment)
{
nodeLeft!.Evaluate(_environment);
nodeRight!.Evaluate(_environment);
if (nodeLeft.Kind != ExpressionKind.Number || nodeRight.Kind != ExpressionKind.Number)
{
System.Console.WriteLine($"! SEMANTIC ERROR: \"{Operator}\" cannot be used between \"{nodeLeft.Kind}\" and \"{nodeRight.Kind}\".");
throw new Exception();
}
value = (double)nodeLeft.value! + (double)nodeRight.value!;
}
public override object? GetValue() => value;
}
El metodo principal de la clase es
Evaluate
el cual comprueba que el usuario este intentando sumar dos numeros, de no ser el caso lanza una exepcion, de ser el caso devuelve la suma de ambos numeros que guarda como nodos izquierdo y derecho respectivamente.
Ejemplo del metodo
Sum
.
Ejemplo del metodo
Difference
.
Ejemplo del metodo
Modulo
.
Ejemplo del metodo
Quotient
.
Ejemplo del metodo
Product
.
Ejemplo del metodo
Power
.
Error: Dado que la division por 0 no esta definida en el metodo Evaluate
de la clase dada se debe hacer una verificacion.
Ejemplo del error de la division por 0.
En este caso solo tenemos una operacion de muestra, que seria el caso de @
la concatenacion, pero este apartado queda abierto para la implementacion de nuevas expresiones binarias que no impliquen elementos boobleanos en su totalidad o numeros en su totalidad.
public class Concatenation : BinaryExpression
{
public Concatenation(TokenKind _operator, Expression nodeLeft, Expression nodeRight) :
base(_operator, nodeLeft, nodeRight)
{
Kind = ExpressionKind.String;
}
public override ExpressionKind Kind { get; set; }
public override object? value { get; set; }
public override void Evaluate(Environment _environment)
{
nodeLeft!.Evaluate(_environment);
nodeRight!.Evaluate(_environment);
value = nodeLeft.GetValue()!.ToString()! + nodeRight.GetValue()!.ToString()!;
}
public override object? GetValue() => value;
}
El funcionamiento es de forma omologa al de la suma.
Luego de haber observado las expresiones basicas del lenguaje procedamos a analizar las expresiones endemicas de este.
En HULK es posible declarar variables usando la expresión let-in
. En general, una expresión let-in
consta de una o más declaraciones de variables, y un cuerpo, que puede ser cualquier expresión donde además se pueden utilizar las variables declaradas en el let
. Fuera de una expresión let-in
las variables dejan de existir. El valor de retorno de una expresión let-in
es el valor de retorno del cuerpo.
public class Let_In : Expression
{
public override Environment? environment { get; set; }
public Expression parameter;
public override ExpressionKind Kind { get; set; }
public Dictionary<string, Expression> Variables { get; set; }
public override object? value { get; set; }
public Let_In(Expression _parameter, Environment _environment) : base(_environment)
{
Variables = new Dictionary<string, Expression>();
Kind = ExpressionKind.Temp;
parameter = _parameter;
}
public override void Evaluate(Environment _environment)
{
parameter.Evaluate(environment!);
value = parameter.GetValue();
//make sure the type of element u are using
//! SEMANTIC ERROR: "Sum" cannot be used between "Number" and "Temp".
if (value is bool) { Kind = ExpressionKind.Bool; }
if (value is string) { Kind = ExpressionKind.String; }
if (value is double) { Kind = ExpressionKind.Number; }
}
public override object? GetValue() => value;
}
Implementacion de la clase
Let_In
Adentremonos en la implementacion de esta clase mediante un ejemplo:
print(7 + (let x = 2 in x * x));
Omitiendo la primera parte que se explicara posteriormente llegamos a la indentificacion del Let in en el parser. Una vez hecho esto procedemos a llamar a la funcion ParseLet_in
Expression ParseLet_in(Environment privateEnvironment)
{
Eat();
Let_In letIn = new(null!, privateEnvironment.CreateChild());
IndexVarible(letIn.environment!);
WatchFor(TokenKind.InKeyWord);
letIn.parameter = ParseExpressionLv1(letIn.environment!);
return letInExpression;
}
Este metodo se encargara de indexar la variable con el valor otorgado por el usario.
Analicemos el metodo IndexVariable
void IndexVarible(Environment letInEnvironment)
{
WatchFor(TokenKind.Identifier);
string name = PreviousToken().GetTokenName();
WatchFor(TokenKind.EqualEqual);
letInEnvironment.variables.Add(name, ParseExpressionLv1(letInEnvironment));
if (currentToken.Kind == TokenKind.Comma)
{
Eat();
IndexVarible(letInEnvironment);
}
}
Primeramente nos aseguramos mediante el metodo
WatchFor
que luego de un let lo que venga sea un identificador (una variable). Guardamos el nombre asignado por el usuario.
Posteriormente verificamos que tenga la instruccion de asignacion
=
Para finalizar se indexa en el estado local del let in parseando el miembro derecho de la asignacion
=
(environment)(posteriormente se explicara detalladamente lo que es el environment).
Una vez concluidos estos pasos, se procede a regresar en la recursion del metodo ParseLet_in
.
Procedemos a hacer el
WatchFor
para encontrar la keywordin
que indicara posteriormente la instruccion a realizar.
Procedemos a parsear el miembro derecho del
in
.
Para finalmente retornar una Expresion del tipo
Let_in
Este caso en particular retorna 11.
Las condiciones en HULK se implementan con la expresión if-else
, que recibe una expresión booleana entre paréntesis, y dos expresiones para el cuerpo del if
y el else
respectivamente. Siempre deben incluirse ambas partes. Como if-else
es una expresión, se puede usar dentro de otra expresión (al estilo del operador ternario en C#):
public class If_Else : Expression
{
public Expression condition;
public Expression nodeLeft;
public Expression nodeRight;
public If_Else(Expression _condition, Expression _nodeLeft, Expression _nodeRight) : base(null!)
{
condition = _condition;
nodeLeft = _nodeLeft;
nodeRight = _nodeRight;
Kind = ExpressionKind.Temp;
}
public override ExpressionKind Kind { get; set; }
public override object? value { get; set; }
public override Environment? environment { get; set; }
public override void Evaluate(Environment _environment)
{
condition!.Evaluate(_environment);
if (condition.value is true)
{
nodeLeft.Evaluate(_environment);
value = nodeLeft.value;
Kind = nodeLeft.Kind;
}
else
{
nodeRight.Evaluate(_environment);
value = nodeRight!.GetValue();
Kind = nodeRight.Kind;
}
}
public override object? GetValue() => value;
}
Implementacion de la clase
If_Else
Analicemos la clase mediante el ejemplo:
let a = 42 in if (a % 2 == 0) print("Even") else print("odd");
Procesamos la primera parte como acabamos de observar en el Let_in
. Una vez en el parse tengamos procesado todo el let
y nos encontremos en el in
pasamos a parsear el miembro derecho del in, donde nos encontraremos con el metodo ParseIf
.
Expression PaseIf(Environment privateEnvironment)
{
Eat();
If_Else If = new(null!, null!, null!);
If.condition = ParseExpressionLv1(privateEnvironment);
if (currentToken.Kind == TokenKind.ElseKeyWord)
{
System.Console.WriteLine($"! SYNTAX ERROR: if_else expression isnt complete.");
throw new Exception();
}
If.nodeLeft = ParseExpressionLv1(privateEnvironment);
WatchFor(TokenKind.ElseKeyWord);
If.nodeRight = ParseExpressionLv1(privateEnvironment);
return If;
}
Se parsea la condicion con el metodo
ParseExpressionLv1
el cual explicaremos detalladamente mas adelante, el cual se encarga de ir parseando recursivamente los elementos dentro de la condicionalIf
.
Se eplica como queda parseado de una mejor manera mediante un diagrama.