This is the sixth part of the YAUL series. For your convenience you can find other parts in the table of contents in Part 1 — Introduction
We continue developing custom language on .NET platform using LINQ expressions. Today we are going to implement conditional operator.
Grammar
We start with PLY grammar. Code for if
is pretty simple:
1 2 3 4 5 6 7 8 9 10 11 12 |
def p_if_else_with_else(p): """if_else : IF '(' cond_expr ')' block_or_statement ELSE block_or_statement """ p[0] = Compiler.If() p[0].Condition = p[3] p[0].TrueBlock = p[5] p[0].FalseBlock = p[7] def p_if_else_without_else(p): """if_else : IF '(' cond_expr ')' block_or_statement""" p[0] = Compiler.If() p[0].Condition = p[3] p[0].TrueBlock = p[5] |
We define if
as keyword followed by condition in parenthesis, and statements. We need to handle matching else
block. This part is a common source of ambiguity in parsers — imagine the following code:
1 2 3 4 5 |
if (something) if (something2) print("Conditions met") else print ("Condition not met") |
Code is poorly formatted deliberately. We have two if
s and only one else
— the question is: is the else
clause matching first or second if
? This problem occurs in most of today’s languages and is usually solved by assumption that the else
matches closes if
. So we should parse this code as:
1 2 3 4 5 6 |
if (something) { if (something2) print("Conditions met") else print ("Condition not met") } |
and not as:
1 2 3 4 5 6 |
if (something) { if (something2) print("Conditions met") } else { print ("Condition not met") } |
In grammar’s terms this is known as reduce-reduce conflict.
Let’s move on. We need to parse conditions:
1 2 3 4 5 6 7 8 9 10 11 |
def p_cond_expr(p): """cond_expr : expr EQEQ expr | expr GTEQ expr | expr LSEQ expr | expr NTEQ expr | expr '>' expr | expr '<' expr""" p[0] = Compiler.BinaryOperation() p[0].Left = p[1] p[0].Right = p[3] p[0].Sign = p[2] |
This part is pretty easy. We handle common relational operators making a binary operation.
C# part
Binary operation was described in previous part. Now code for if
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
using System.Linq.Expressions; namespace Compiler { public class If : IStatement { public IExpression Condition { get; set; } public StatementBlock TrueBlock { get; set; } public StatementBlock FalseBlock { get; set; } public If() { TrueBlock = new StatementBlock(); FalseBlock = new StatementBlock(); } public Expression Accept(IVisitor visitor) { if (FalseBlock.Statements != null) { return Expression.IfThenElse(Condition.Accept(visitor), TrueBlock.Accept(visitor), FalseBlock.Accept(visitor)); } return Expression.IfThen(Condition.Accept(visitor), TrueBlock.Accept(visitor)); } public void FindLabels(IVisitor visitor) { TrueBlock.FindLabels(visitor); if (FalseBlock.Statements != null) { FalseBlock.FindLabels(visitor); } } } } |
Nothing difficult here. We have only two cases to handle, if
with matching else
or without. We also need to recursively find labels in blocks.
Summary
We are now able to handle conditions in YAUL. Next time we will examine basic loop concept.