summaryrefslogtreecommitdiff
path: root/compiler/Methods/Conditional.cs
blob: b019be75c3788c7aed80608797ed48636b4488fe (plain)
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
using AtLangCompiler.ILEmitter;
using System.Reflection.Emit;

namespace AtLangCompiler.Methods;

internal class IfToken : ILexerTokenConfig
{
    public IReadOnlyDictionary<string, TokenType> TokenStrings => new Dictionary<string, TokenType>
    {
        { "@if", TokenType.IF },
        { "@else", TokenType.ELSE }
    };
}

internal class IfStatement : ASTNode
{
    public ASTNode LeftExpr { get; }
    public ASTNode RightExpr { get; }
    public List<ASTNode> IfBody { get; }
    public List<ASTNode> ElseBody { get; }

    public IfStatement(ASTNode left, ASTNode right, List<ASTNode> ifBody, List<ASTNode> elseBody)
    {
        LeftExpr = left; RightExpr = right;
        IfBody = ifBody; ElseBody = elseBody;
    }
}

[ParserFor(TokenType.IF, inputTokens: 2, inputSeparator: "==", hasBody: true)]
[ParserFor(TokenType.ELSE, hasBody: true, prerequisiteToken: TokenType.IF)]
internal class ConditionalStatementParser : IStatementParser
{
    public ASTNode ParseStatement(Parser parser)
    {
        parser.Eat(TokenType.IF);
        parser.Eat(TokenType.LPAREN);
        ASTNode left = parser.ParseExpression();

        if (parser.current.Type == TokenType.EQEQ)
        {
            parser.Eat(TokenType.EQEQ);
        }
        else
        {
            throw new Exception("Only == is supported.");
        }

        ASTNode right = parser.ParseExpression();
        parser.Eat(TokenType.RPAREN);
        parser.Eat(TokenType.LBRACE);
        List<ASTNode> ifBody = new List<ASTNode>();
        while (parser.current.Type != TokenType.RBRACE && parser.current.Type != TokenType.EOF)
        {
            ASTNode? s = parser.ParseStatement();
            if (s != null) ifBody.Add(s);
        }
        parser.Eat(TokenType.RBRACE);

        List<ASTNode> elseBody = new List<ASTNode>();
        if (parser.current.Type == TokenType.ELSE)
        {
            parser.Eat(TokenType.ELSE);
            parser.Eat(TokenType.LBRACE);
            while (parser.current.Type != TokenType.RBRACE && parser.current.Type != TokenType.EOF)
            {
                ASTNode? s = parser.ParseStatement();
                if (s != null) elseBody.Add(s);
            }
            parser.Eat(TokenType.RBRACE);
        }
        return new IfStatement(left, right, ifBody, elseBody);
    }
}

[EmitterFor(typeof(IfStatement))]
internal class Conditional : IMethodEmitter<IfStatement>
{
    private readonly ILGenerator il;
    private readonly ILMethodEmitterManager emitter;

    public Conditional(ILGenerator il, ILMethodEmitterManager emitter)
    {
        this.il = il ?? throw new ArgumentNullException(nameof(il));
        this.emitter = emitter ?? throw new ArgumentNullException(nameof(emitter));
    }

    public void EmitIL(IfStatement node)
    {
        // if (EvalExpr(Left) == EvalExpr(Right)) { IfBody } else { ElseBody }
        emitter.EmitStatement(node.LeftExpr);
        emitter.EmitStatement(node.RightExpr);

        // string.Equals(string, StringComparison.Ordinal)
        System.Reflection.MethodInfo strEquals = typeof(string).GetMethod("Equals", [typeof(string), typeof(StringComparison)])!;
        Label labelElse = il.DefineLabel();
        Label labelEnd = il.DefineLabel();

        il.Emit(OpCodes.Ldc_I4, (int)StringComparison.Ordinal);
        il.Emit(OpCodes.Call, strEquals);
        il.Emit(OpCodes.Brfalse, labelElse);

        // If-body
        foreach (ASTNode st in node.IfBody)
        {
            emitter.EmitStatement(st);
        }

        il.Emit(OpCodes.Br, labelEnd);

        // Else-body
        il.MarkLabel(labelElse);
        foreach (ASTNode st in node.ElseBody)
        {
            emitter.EmitStatement(st);
        }

        il.MarkLabel(labelEnd);
    }
}