-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprocedure_info.jai
More file actions
379 lines (306 loc) · 16 KB
/
procedure_info.jai
File metadata and controls
379 lines (306 loc) · 16 KB
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
/*
Procedure Info
Questions to answer:
What kinds of node nodes will we need to deal with other than identifier?
Probably #procedure_of_call or #bake_parameters as well...
When will argument names actually be available, and when will they not?
Do we really care about return value names?
TODO:
create several test cases using different types of procedure expressions
access through struct members, array indexing, #bake_arguments, #procedure_of_call
does the struct member case actually matter if the member is nonconstant?
array case probably does not matter at all, or at least it's not very important
write a procedure to just dump code nodes to the console so that I can see what particular expressions look like as code nodes
NOTE:
Looking at Code_Procedure_Call, and how #procedure_of_call actually works, it has me wondering:
Could we use the argument values provided in procedure_of_call as an override on the default values that are provided to Lead Sheets?
Technically, also, the user could just optionally provide a their own get_default_argument_value for a procedure.
This is probably the way to go, and we just provide some procedures to create a procedure_info struct in a somewhat pre-checked way
user can already just bake parameters if they want to, and manually providing default values seems a bit smarter, tbh
TODO:
think about what things we could actually do with return value names
maybe would be useful for something similar to or_return?
TODO: eventually move this to the Reflect module
*/
/*
This is a helper function to track down a procedure header given some general procedure-typed expression.
There are probably some edge cases I am missing here so if you pass something that seems like it should work, but it doesn't then let me know. (@u0 on Discord)
*/
get_procedure_header :: (node: *Code_Node) -> *Code_Procedure_Header #compile_time {
if node == null return null;
if node.kind == {
case .PROCEDURE_HEADER;
return xx node;
case .IDENT;
ident := node.(*Code_Ident);
return get_procedure_header(ident.resolved_declaration);
case .DIRECTIVE_BAKE;
bake := node.(*Code_Directive_Bake);
return get_procedure_header(bake.procedure_call);
case .TYPE_INSTANTIATION;
type_inst := node.(*Code_Type_Instantiation);
if type_inst.type_valued_expression {
result := get_procedure_header(type_inst.type_valued_expression);
if result return result;
}
// TODO: other cases?
case .BINARY_OPERATOR;
binary_operator := node.(*Code_Binary_Operator);
if binary_operator.operator_type == {
case #char ".";
return get_procedure_header(binary_operator.right);
}
case .DECLARATION;
declaration := node.(*Code_Declaration);
if declaration.type_inst {
result := get_procedure_header(declaration.type_inst);
if result return result;
}
return get_procedure_header(declaration.expression);
case .DIRECTIVE_INSERT;
insert := node.(*Code_Directive_Insert);
return get_procedure_header(insert.expansion);
case .PROCEDURE_CALL;
call := node.(*Code_Procedure_Call);
// if (call.flags & .RETURNS_PROCEDURE_POINTER_ONLY)
// || (call.flags & 0x400) { // this means procedure_of_call was used
return get_procedure_header(call.resolved_procedure_expression);
// }
}
return null;
}
Procedure_Info :: struct {
// NOTE: We do this funky overlay thing so that we can return this struct as a constant from a #run,
// but you can still access the type info directly through the using on the type info.
// Note also that the member 'type' here refers to the Type_Info_Tag in the using'd type info!
__type: Type;
#overlay(__type) using __type_info: *Type_Info_Procedure;
arguments: [] Argument;
returns: [] Argument;
// TODO: Maybe we should have separate arrays for each rather than an array of structs,
// main reason being that we already have argument/return types in a separate arrays in __type_info.
Argument :: struct {
name: string;
flags: Flags;
Flags :: enum_flags {
NONE :: 0;
HAS_DEFAULT_VALUE :: 1;
IS_VARARGS;
}
}
get_default_argument_value: (index: int, provided_storage := null) -> bool, Any;
notes: [] string;
}
/*
Generates a constant Procedure_Info and returns a pointer to that struct.
This could certainly use some improvement and return other information about a procedure that would be useful to know at runtime.
The implementation here is by no means complete, but
*/
get_procedure_info :: ($PROCEDURE_EXPRESSION: Code, $SCOPE := #caller_code, $LOC := #caller_location) -> *Procedure_Info #expand {
PROCEDURE :: #insert PROCEDURE_EXPRESSION;
PROCEDURE_TYPE :: type_of(PROCEDURE);
PROCEDURE_TYPE_INFO :: type_info(PROCEDURE_TYPE);
// Dumping all the argument types here is necessary for our code generation below.
ARGUMENT_TYPES :: #run () -> [] Type {
types := NewArray(PROCEDURE_TYPE_INFO.argument_types.count, Type,, temp);
for PROCEDURE_TYPE_INFO.argument_types {
types[it_index] = get_type(it);
}
return types;
}();
/*
This #run directive does the bulk of the work in this macro.
In addition to generating the basic data arrays for arguments, return values, and notes,
it also generates the Procedure_Info's internal `get_default_argument` procedure.
TODO: I should say some more about this get_default_argument procedure to explain
why we need to generate it with code nodes rather than as a string...
*/
ARGUMENTS, RETURNS, NOTES, GET_DEFAULT_ARGUMENT_CODE :: #run -> [] Procedure_Info.Argument, [] Procedure_Info.Argument, [] string, Code {
root := compiler_get_nodes(PROCEDURE_EXPRESSION);
procedure_header := get_procedure_header(root);
if procedure_header == null {
builder: String_Builder;
append(*builder, "Unable to resolve procedure header from expression: ");
Program_Print.print_expression(*builder, root);
error_string := builder_to_string(*builder,, temp);
compiler_report(error_string, loc = LOC);
}
if procedure_header.procedure_flags & .POLYMORPHIC {
compiler_report("Cannot get procedure info for a polymorphic procedure. Try using #procedure_of_call or #bake_arguments to monomorph the procedure before passing it to get_procedure_info.", loc = LOC);
}
arguments := NewArray(procedure_header.arguments.count, Procedure_Info.Argument,, temp);
for procedure_header.arguments {
flags: Procedure_Info.Argument.Flags;
if it.expression {
flags |= .HAS_DEFAULT_VALUE;
}
if it.type_inst && (it.type_inst.inst_flags & .VARARGS) {
flags |= .IS_VARARGS;
}
arguments[it_index] = .{ it.name, flags };
}
returns := NewArray(procedure_header.returns.count, Procedure_Info.Argument,, temp);
for procedure_header.returns {
returns[it_index] = .{ it.name, .NONE };
}
notes := NewArray(procedure_header.notes.count, string,, temp);
for procedure_header.notes {
notes[it_index] = it.text;
}
get_default_argument_value_procedure_code := ifx 1 {
push_allocator(temp);
cases: [..] *Code_Case;
array_reserve(*cases, procedure_header.arguments.count);
// we reuse these nodes for each argument
ident_ARGUMENT_TYPES := make_ident("ARGUMENT_TYPES");
for procedure_header.arguments {
if it.expression {
code_case := New(Code_Case);
code_case.condition = make_integer_literal(it_index);
// TODO: This could make for a good test case for the code formatter.
// We will need a new mechanism to insert the integer literal, maybe we add an explicit capture system and that solves our scoping issue as well...
then_block_format := #code {
ret := New(void, initialized = false);
ret.* = #insert,scope(SCOPE) #code 0;
return true, ret.*;
};
code_case.then_block = compiler_get_nodes(then_block_format).(*Code_Block);
// replace `void` with actual argument type
argument_type_expression := make_binary_operator(.ARRAY_SUBSCRIPT, ident_ARGUMENT_TYPES, make_integer_literal(it_index));
code_case.then_block
.statements[0].(*Code_Declaration)
.expression.(*Code_Procedure_Call)
.arguments_unsorted[0].expression = argument_type_expression;
// we need to specially replace the #caller_location directive, since it cannot be used as a general expression
if it.expression.kind == .DIRECTIVE_LOCATION
&& it.expression.(*Code_Directive_Location).is_caller_location {
code_case.then_block
.statements[1].(*Code_Binary_Operator)
.right = compiler_get_nodes(#code #location(PROCEDURE_EXPRESSION));
} else {
// replace `#code 0` in insert with it.expression
code_case.then_block
.statements[1].(*Code_Binary_Operator)
.right.(*Code_Directive_Insert)
.expression.(*Code_Directive_Code)
.expression = it.expression;
}
array_add(*cases, code_case);
}
}
switch_statement := make_if_case(make_ident("index"), cases);
default_return_statement := compiler_get_nodes(#code return false, Any.{});
body_block := make_block(switch_statement, default_return_statement);
// We use the procedure header from the declaration of Procedure_Info.get_default_argument_value so that we don't have to build it manually here.
// Maybe this is an overly lazy approach, but it's also just kind of cool to show that this can be done.
exemplar_header := get_procedure_header(
compiler_get_nodes(code_of(Procedure_Info.get_default_argument_value))
);
header := make_procedure_header(exemplar_header.*, body_block);
// And the final result of this massive ifx...
make_declaration("get_default_argument_value", null, header, .IS_CONSTANT);
}
// print_code(get_default_argument_value_procedure_code);
return arguments, returns, notes, compiler_get_code(get_default_argument_value_procedure_code);
};
#insert GET_DEFAULT_ARGUMENT_CODE;
PROCEDURE_INFO :: Procedure_Info.{
__type = PROCEDURE_TYPE,
arguments = ARGUMENTS,
returns = RETURNS,
get_default_argument_value = get_default_argument_value,
notes = NOTES,
};
return *PROCEDURE_INFO;
}
get_default_argument_value :: (info: Procedure_Info, argument_name: string) -> bool, Any {
for info.arguments {
if it.name == argument_name {
if !(it.flags & .HAS_DEFAULT_VALUE) return false, Any.{};
return true, it.get_default();
}
}
return false, Any.{};
}
#scope_module
#import "Compiler";
Program_Print :: #import "Program_Print";
// with_builder :: ($code: Code) -> string {
// builder: String_Builder;
// // find builder argument and pass this builder, then insert code nodes
// #insert -> Code {
// }
// return builder_to_string(*builder);
// }
// TODO: move to some compiler utils module, eventually
print_code_nodes :: (builder: *String_Builder, node: *Code_Node, indent := 0) {
if node == null return;
put :: (format: string, args: ..Any, newline := true) #expand {
for 1..indent append(builder, " ");
print(builder, format, ..args);
if newline append(builder, "\n");
}
put("kind: %", node.kind);
put("type: %", as_type(node.type));
// put("expression: ", newline = false);
// Program_Print.print_expression(builder, node);
// append(builder, "\n");
if node.kind == {
case .LITERAL;
literal := node.(*Code_Literal);
put("value_type: %", literal.value_type);
// TODO: get_literal_value_as_any, print that here.
case .IDENT;
ident := node.(*Code_Ident);
put("name: %", ident.name);
put("resolved_declaration: %", ident.resolved_declaration);
print_code_nodes(builder, ident.resolved_declaration, indent + 1);
// TODO: maybe we should collect all resolved declarations and display them at the end
// could just show the pointer or serial of the resolved declaration here
case .DECLARATION;
decl := node.(*Code_Declaration);
print_code_nodes(builder, decl.expression, indent + 1);
// Program_Print.print_expression(builder, decl.expression);
case .DIRECTIVE_INSERT;
insert := node.(*Code_Directive_Insert);
print_code_nodes(builder, insert.expansion, indent + 1);
case .UNARY_OPERATOR;
op := node.(*Code_Unary_Operator);
str := operator_to_string(xx op.operator_type);
str = ifx str else string.{ 1, xx *op.operator_type };
put("operator_type: %", );
put("subexpression:");
print_code_nodes(builder, op.subexpression, indent + 1);
case .BINARY_OPERATOR;
op := node.(*Code_Binary_Operator);
str := operator_to_string(xx op.operator_type);
str = ifx str else string.{ 1, xx *op.operator_type };
put("operator_type: %", str);
put("left:");
print_code_nodes(builder, op.left, indent + 1);
put("right:");
print_code_nodes(builder, op.right, indent + 1);
case .PROCEDURE_CALL;
call := node.(*Code_Procedure_Call);
put("procedure_expression: %", call.procedure_expression);
print_code_nodes(builder, call.procedure_expression, indent + 1);
put("resolved_procedure_expression: %", call.resolved_procedure_expression);
print_code_nodes(builder, call.resolved_procedure_expression, indent + 1);
// put("overloads:");
// for overloads.* {
// print_code_nodes(builder, it, indent + 1);
// }
if call.flags {
put("flags: %", call.flags);
if call.flags & .INLINE_YES { put(" INLINE_YES"); }
if call.flags & .INLINE_NO { put(" INLINE_NO"); }
if call.flags & .RETURNS_PROCEDURE_POINTER_ONLY { put(" RETURNS_PROCEDURE_POINTER_ONLY"); }
if call.flags & .NO_DEBUG { put(" NO_DEBUG"); }
if call.flags & .IS_MODULE_PARAMETERS { put(" IS_MODULE_PARAMETERS"); }
}
case .DIRECTIVE_BAKE;
bake := node.(*Code_Directive_Bake);
put("procedure_call:");
print_code_nodes(builder, bake.procedure_call, indent + 1);
}
}