-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresult.jai
More file actions
215 lines (166 loc) · 8.53 KB
/
result.jai
File metadata and controls
215 lines (166 loc) · 8.53 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
/*
Notes about error handling:
currently, we aren't using result objects because of the stack frame bloat issue...
we may reevaluate this in the future if it provides us with considerably better or_return semantics, but it seems like we can do just as well with a single error object that gets set in the script itself.
only typechecking really makes much use of checking the script.error for flow control,
and TBH we could probably go back to simply checking if the returned value type is null for all internal calls to typecheck_node, only checking script.error when we really need to.
this would probably be just a bit faster too.
but in either case, we will want to check the script.error for typechecking callbacks, so that they may return null when they simply don't want to override typechecking for a given node
there aren't really many error cases during execution, and we simply use a bool return value on execute_node for flow control. it is then just assumed that the error should always be set before returning false
again, we may make typechecking behave the same later on
evaluation incomplete
parsing incomplete
lexing incomplete, really need to figure out better solution for propogating up lexer errors
for all cases, we still need to further improve the actual error recording and reporting
*/
// Result :: struct(T: Type) {
// error: Error;
// value: T;
// }
// operator ! :: inline (result: Result) -> bool { return result.error.type != .NO_ERROR; }
// make_result :: inline (value: $T) -> Result(T) {
// return .{ value = value };
// }
// // only for errors, since we have binary compatibility in that case
// recast_error :: (result: Result($T), $to_type: Type) -> Result(to_type) {
// assert(result.error != .NO_ERROR);
// return .{ error = result.error };
// }
// or_return :: (result: Result($T), $R: Type) -> T #expand {
// if !result `return recast_error(R, result);
// return result.value;
// }
// or_return :: (result: Result($T)) -> T #expand {
// if !result `return result;
// return result.value;
// }
// or_default :: inline (result: Result($T), default: T) -> T {
// if !result return default;
// return result.value;
// }
// ===== Error Struct =====
// instead of just returning all errors as bool + string, we are using an error enum and union
// one reason for this is that we want to be able to recover from certain types of errors that may occur, particularly during typechecking
// the other reason is that we can defer formatting an error into a string until we have completely unwound the stack to the point where the error is handled or reported
// this means we can attach more information as we unwind, which should give better context to some error messages
// another benefit is that in the future we could aggregate multiple errors before printing them all out
// s64 is definitely overkill but whatever
Error_Type :: enum s64 {
NO_ERROR :: 0;
// broad classes of errors, stored in upper 32 bits
// this is done so that we can | the subtype with broader type to determine if a subtype belongs to a certain class of error
// some of these classes of errors correspond to Error union subtypes with additional information which is specific to that error
GENERAL_ERROR :: 1 << 32;
LEXER_ERROR :: 2 << 32;
PARSE_ERROR :: 3 << 32;
TYPECHECK_ERROR :: 4 << 32;
EVAL_ERROR :: 5 << 32;
EXECUTION_ERROR :: 6 << 32;
USER_ERROR :: 7 << 32;
CLASS_MASK :: 0xffffffff_00000000;
// error subtypes stored in lower 32 bits
MISSING_TYPE_HINT :: TYPECHECK_ERROR + 1;
UNRESOLVED_IDENTIFIER :: TYPECHECK_ERROR + 2;
FAILED_IMPLICIT_CAST :: TYPECHECK_ERROR + 3;
FAILED_OVERLOAD_RESOLUTION :: TYPECHECK_ERROR + 4;
UNEXPECTED_TOKEN :: LEXER_ERROR + 1;
UNEXPECTED_CHARACTER :: LEXER_ERROR + 2;
UNEXPECTED_EOF :: LEXER_ERROR + 3;
}
Error :: union {
using
base: Error_Base;
parse: Parse_Error;
lexer: Lexer_Error;
typecheck: Typecheck_Error;
}
Error_Base :: struct {
type: Error_Type;
message: string;
location: Source_Code_Location; // NOTE: This is the Jai location where the error struct itself was created, not the location in the script source.
}
Lexer_Error :: struct {
using
base: Error_Base;
token: Token;
}
Parse_Error :: struct {
using
base: Error_Base;
node: *Node;
}
Typecheck_Error :: struct {
using
base: Error_Base;
node: *Node;
procedure_overload_report: Procedure_Overload_Report;
}
get_class :: inline (error: Error) -> Error_Type { return error.type & .CLASS_MASK; }
format_error :: (builder: *String_Builder, script: *Script, append_jai_location := true, append_script_location := true) {
if !has_error(script) return;
error := *script.error;
if append_jai_location {
print(builder, "%:%,%: ", error.location.fully_pathed_filename, error.location.line_number, error.location.character_number);
}
if get_class(error) == {
case .LEXER_ERROR;
token := error.lexer.token;
if append_script_location {
print(builder, "\n%:%,%: ", token.location.fully_pathed_filename, token.location.line_number, token.location.character_number);
}
if error.type == {
case .UNEXPECTED_TOKEN;
print(builder, "Unexpected token: % %", token.text, token.trivia);
case .UNEXPECTED_CHARACTER;
print(builder, "Unexpected character: % %", token.text, token.trivia);
}
case .TYPECHECK_ERROR;
node := error.typecheck.node;
if node {
if append_script_location {
print(builder, "\n%:%,%: ", node.location.fully_pathed_filename, node.location.line_number, node.location.character_number);
}
if error.type == {
case .UNRESOLVED_IDENTIFIER;
assert(node.node_type == Node_Identifier);
print(builder, "Unresolved identifier: '%'\n", node.(*Node_Identifier).name);
case .MISSING_TYPE_HINT;
print(builder, "Unable to resolve type due to missing type hint.\n");
case .FAILED_IMPLICIT_CAST;
// NOTE: currently the full error is just in error.message
case .FAILED_OVERLOAD_RESOLUTION;
format_procedure_overload_report(builder, script, error.typecheck.procedure_overload_report);
}
} else {
print(builder, "[node is null! error type is %] ", error.type);
}
case; #through; // temporary, until other cases are implemented
case .GENERAL_ERROR;
}
append(builder, error.message);
}
format_error :: (script: *Script, append_jai_location := true, append_script_location := true) -> string {
builder: String_Builder;
format_error(*builder, script, append_jai_location, append_script_location);
return builder_to_string(*builder);
}
// set_error :: (script: *Script, type: Error_Type, format: string, args: ..Any, loc := #caller_location) {
// script.error = .{ type = type, message = tprint(format, ..args), location = loc };
// } @PrintLike
set_general_error :: (script: *Script, format: string, args: ..Any, loc := #caller_location) {
script.error = .{ type = .GENERAL_ERROR, message = tprint(format, ..args), location = loc };
} @PrintLike
set_parse_error :: (script: *Script, format: string = "", args: ..Any, loc := #caller_location) {
// assert(type & .CLASS_MASK == .PARSE_ERROR);
script.error = .{ type = .PARSE_ERROR, message = tprint(format, ..args), location = loc };
} @PrintLike
set_typecheck_error :: (script: *Script, format: string = "", args: ..Any, node: *Node, type := Error_Type.TYPECHECK_ERROR, loc := #caller_location) {
assert(type & .CLASS_MASK == .TYPECHECK_ERROR);
script.error = .{ type = type, message = tprint(format, ..args), location = loc };
script.error.typecheck.node = node;
} @PrintLike
set_execution_error :: (script: *Script, format: string = "", args: ..Any, node: *Node, type := Error_Type.EXECUTION_ERROR, loc := #caller_location) {
assert(type & .CLASS_MASK == .EXECUTION_ERROR);
script.error = .{ type = type, message = tprint(format, ..args), location = loc };
// script.error.exec.node = node;
} @PrintLike