-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdirective.jai
More file actions
245 lines (201 loc) · 10.8 KB
/
directive.jai
File metadata and controls
245 lines (201 loc) · 10.8 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
/*
Directives are just procedures that are evaluated as soon as they are parsed.
Syntactically, all directives follow the form of a procedure call, but the name of the directive is prefixed with a `#`.
Directives have a very flexible interface, allowing the user to pass as many arguments as they wish,
and optionally, to fill in some or all of those arguments with user data values.
Directives can receive arguments directly as a *Node, manipulate those nodes, and return new nodes to replace the directive at runtime.
Because they receive a pointer to the script, directives can theoretically be used to implement custom parsing logic (although I have not tried this yet myself).
Interface Rules:
Currently, only native Jai procedures are supported (not #c_call). This will likely change in the future.
Unlike other procedures, directives do not support overloading (because the interface is already so loosey-goosey).
The first argument must always be a *Directive_Call, which receives the currently executing script and the directive node as context.
The next N arguments are then filled in by the provided user data values.
The remaining arguments are to be provided as parameters to the directive call in the script text itself.
User data values are provided as Any's, so the underlying values must remain valid for the lifetime of the script.
All user data values are typechecked against the procedure signature, because I don't trust you (or myself) not to slip up.
Whether an expression passed as a parameter to a directive is received as an evaluated value or as raw nodes is determined by the formal type of argument.
If the argument's formal type is *Node, then the raw nodes will be provided, otherwise the argument expression will be evaluated and typechecked before being passed to the directive.
The directive may return either one or two values:
The first value is optional, and may be of any type.
If it is a *Node, that node will replace the directive for the purposes of typechecking and runtime execution.
Otherwise, the returned value will be inserted as a literal.
The second (or only) value must be a bool which represent the success or failure of the directive.
If this value is false, the script will cease parsing and report the error.
IMPORTANT:
Directives are always called twice, once during parsing and once during typechecking.
In order to ensure that the main logic of your directive is not doubyl executed, you need to check the 'phase' value passed in the Directive_Call.
TODO:
directive should be able to accept Any as a valid argument type as well
will require changes in dyncall. I guess I overlooked Any's as procedure arguments
operator-style directives
option to sub-out or reset directive user_data if need be
user can do this manually if they're getting that technical
consider if we should just remove the extra complication around return types setting the replacement node and just let the user do that manually in the directive body
since the user is given a pointer to the directive node, he can just set it himself...
*/
Script_Phase :: enum { PARSE; TYPECHECK; /*.LOWER_TO_BYTECODE;*/ };
Directive :: struct {
using #as base: External_Procedure;
user_data: [] Any;
}
Directive_Call :: struct {
phase: Script_Phase;
script: *Script;
directive_node: *Node_Directive;
}
can_register_directive :: (proc_info: *Type_Info_Procedure) -> bool {
if proc_info.procedure_flags & .IS_C_CALL {
log("Error: #c_call procedures are not currently supported as directives.");
return false;
}
if proc_info.argument_types.count < 1 {
log("Error: directive must take at least 1 argument for Script pointer.");
return false;
}
if proc_info.argument_types[0] != type_info(*Directive_Call) {
log("Error: directive must take a *Directive_Call as the first argument.");
return false;
}
if proc_info.return_types.count < 1
|| proc_info.return_types.count > 2
|| proc_info.return_types[0] != type_info(bool) {
log("Error: directive must return a bool signifying success as first return value.");
return false;
}
return true;
}
#if USING_DYNCALL {
register_directive :: (script: *Script, name: string, procedure: Any, user_data: ..Any) {
assert(procedure.type.type == .PROCEDURE && can_register_directive(xx procedure.type));
for script.directives {
if it.name == name {
assert(false, "Error: directives do not support overloading.");
}
}
proc_info := procedure.type.(*Type_Info_Procedure);
if proc_info.argument_types.count < user_data.count + 1 {
log("Error: too many user_data values provided to directive.");
return;
}
for user_data {
// TODO: allow using #as, maybe also coerce integers/floats
// if proc_info.argument_types[it_index + 1] != type_info(Any)
// && it.type != proc_info.argument_types[it_index + 1] {
if it.type != proc_info.argument_types[it_index + 1] {
assert(false, "Error: type of user_data value % does not match type of corresponding directive argument. % vs %", it_index + 1, as_type(it.type), as_type(proc_info.argument_types[it_index + 1]));
}
}
array_add(*script.directives, .{
name = name,
procedure = Any_Proc.from(procedure),
user_data = user_data
});
}
} else {
// TODO: could use #caller_code or soemthing to statically check that user_data arguments are of proper types
register_directive :: inline (script: *Script, name: string, procedure: $P, user_data: ..Any) #modify {
proc_info := P.(*Type_Info_Procedure);
if proc_info.type != .PROCEDURE return false, "'procedure' must be a procedure! What are you stupid?";
if !can_register_directive(proc_info) return false, tprint("Unable to register procedure of type: %", P);
return true;
} {
for script.directives {
if it.name == name {
assert(false, "Error: directives do not support overloading.");
}
}
proc_info := P.(*Type_Info_Procedure);
for user_data {
// TODO: allow using #as, maybe also coerce integers/floats
// if proc_info.argument_types[it_index + 1] != type_info(Any)
// && it.type != proc_info.argument_types[it_index + 1] {
if it.type != proc_info.argument_types[it_index + 1] {
assert(false, "Error: type of user_data value % does not match type of corresponding directive argument. % vs %", it_index + 1, as_type(it.type), as_type(proc_info.argument_types[it_index + 1]));
}
}
register_procedure_type(P);
array_add(*script.directives, .{
procedure = Any_Proc.from(procedure),
name = name,
user_data = user_data
});
}
}
evaluate_directive :: (script: *Script, directive: *Node_Directive, phase: Script_Phase) -> bool {
procedure := script.directives[directive.directive_index];
directive_call := Directive_Call.{ phase, script, directive };
// ===== typecheck call =====
first_arg_index := 1 + procedure.user_data.count;
if procedure.argument_types.count != directive.arguments.count + first_arg_index {
log("Error: Incorrect number of arguments provided for directive call. Expected % total, % were pre-filled by user_data, got %.\n", procedure.argument_types.count, procedure.user_data.count, directive.arguments.count);
return false;
}
for directive.arguments {
// skip typechecking node if argument type intends to receive *Node
expected_type := procedure.argument_types[it_index + first_arg_index];
if expected_type == type_info(*Node) continue;
argument_type := typecheck_node(script, it);
if has_error(script) {
log("Error: failed to evaluate argument % for directive call:\n\t%", it_index+1, format_error(script));
return false;
}
if argument_type != expected_type {
log("Error: type mismatch on argument % for directive call. Expected %, got %.\n", it_index+1, as_type(expected_type), as_type(argument_type));
return false;
}
}
// ===== prepare arguments =====
arguments := NewArray(procedure.argument_types.count, Any,, temp);
{
arguments[0] = *directive_call;
argument_index := 1;
for procedure.user_data {
arguments[argument_index] = it;
argument_index += 1;
}
for *directive.arguments {
if procedure.argument_types[it_index + first_arg_index] == type_info(*Node) {
arguments[argument_index] = it.*; // pass *Node itself
} else {
arguments[argument_index] = evaluate_node(script, it.*);
if has_error(script) return false;
}
argument_index += 1;
}
}
// ===== prepare return values =====
// TODO: allow multiple user return values?
return_success: bool;
return_value: Any;
return_values: [] Any;
if procedure.return_types.count > 1 {
return_value = New_Any(procedure.return_types[1],, temp);
return_values = Any.[ return_success, return_value ];
} else {
return_values = Any.[ return_success ];
}
// ===== perform call =====
#if USING_DYNCALL {
if !do_dyncall(script.dyncall_vm, to_any(procedure), arguments, return_values) {
set_general_error(script, "Failed while trying to make dyncall.");
return false;
}
} else {
if !try_calling_procedure_with_wrapper(procedure.proc_info, procedure.pointer, arguments, return_values) {
log("Error: Unable to find wrapper for procedure of type: %", as_type(procedure.proc_info));
return false;
}
}
// ===== handle return values =====
if return_success {
if return_value.type == {
case null; // do nothing!
case type_info(*Node);
// NOTE: node being pointed to is already assumed to be in the script's pool and have valid storage duration
directive.runtime_node = return_value.value_pointer.(**Node).*;
case;
directive.runtime_node = make_literal(script, return_value);
}
}
return return_success;
}