aboutsummaryrefslogtreecommitdiffhomepage
path: root/notes/metaprogramming.txt
blob: 4069da8e0a1764c3157d1395f37755bd8f5b35e2 (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

Metaprogramming
===============

See also "compile_time_execution.txt".

Use cases:
* Centralizing definitions. E.g. an error message might have:
    - an identifier
    - a severity level
    - a message
* "Functions" that can implicitly use variables or return from the outer function.
* "Functions" that create variables or statements
* "Functions" that create types or enum values
* etc.


Solutions:
* Pre-processor
* Typed pre-processor
* Code generation
* Code verification (write the code manually, but have an external tool to verify it)
* Compile-time execution
* Compile-time verification (write the code manually, but have the compiler verify it)
* Dynamic dispatch / runtime interpretation
  (this might actually be faster than code generation due to better I-cache utilization!)
* ...?

Desirable properties:
* Should be type safe
* It would be nice if it can run in a loop to generate repeated code.


Syntax idea 1:

    func print_operator(arena Output out, ref Operator op)
    {
        switch op.kind {
        %each %Operator %{
            case %(Operator.enum_ident):
                print_string(%(Operator.name))
                %if %(Operator.arity == 2) %{
                    print_expr(operator.operand1)
                    print_expr(operator.operand2)
                %} %else %{
                    print_expr(operator.operand1)
                %}
        %}
        }
    }

Syntax idea 2:

    func print_operator(arena Output out, ref Operator op)
    {
        switch op.kind {
        case %(Operator.enum_ident):
            print_string(%(Operator.name))
            %if %(Operator.arity == 2) %{
                print_expr(operator.operand1)
                print_expr(operator.operand2)
            %} %else %{
                print_expr(operator.operand1)
            %}
        }
    }

Syntax idea 3:

    func print_operator(arena Output out, ref Operator op)
    {
        switch op.kind {
        case %enum_ident:
            print_string(%OPERATOR_NAME(%enum_ident))
            %if OPERATOR_ARITY(%enum_ident) == 2 %{
                print_expr(operator.operand1)
                print_expr(operator.operand2)
            %} %else %{
                print_expr(operator.operand1)
            %}
        }
    }

Syntax idea 4 (dynamic). Best solution?

    type OpInfo = struct {
        string name
        int arity
    }
    data OpInfo[OperatorKind] opinfos = [
        .plus = ("plus", 2)
        .minus = ("minus", 2)
        ...
    ]

    func print_operator(arena Output out, ref Operator op)
    {
        OpInfo opinfo = opinfo[op.kind]
        print_string(opinfo.name)
        if opinfo.arity == 2 {
            print_expr(operator.operand1)
            print_expr(operator.operand2)
        } else {
            print_expr(operator.operand1)
        }
    }