GCC Code Coverage Report


Directory: ./
File: src/vm/tree.h
Date: 2023-04-27 00:55:30
Exec Total Coverage
Lines: 8 10 80.0%
Functions: 5 8 62.5%
Branches: 0 0 -%

Line Branch Exec Source
1
2 #ifndef LYTHON_TREE_EVAL_HEADER
3 #define LYTHON_TREE_EVAL_HEADER
4
5 #include "ast/magic.h"
6 #include "ast/ops.h"
7 #include "ast/visitor.h"
8 #include "sema/bindings.h"
9 #include "sema/builtin.h"
10 #include "sema/errors.h"
11 #include "utilities/strings.h"
12
13 namespace lython {
14
15 using PartialResult = Node;
16
17 struct TreeEvaluatorTrait {
18 using StmtRet = PartialResult*;
19 using ExprRet = PartialResult*;
20 using ModRet = PartialResult*;
21 using PatRet = PartialResult*;
22 using Trace = std::true_type;
23
24 enum
25 { MaxRecursionDepth = LY_MAX_VISITOR_RECURSION_DEPTH };
26 };
27
28 struct StackTrace {
29 // The statement point to the line
30 // while the expression points to a specific location in the line
31 StmtNode const* stmt;
32 ExprNode const* expr;
33 };
34
35 /* Tree evaluator is a very simple interpreter that is also very slow.
36 * It takes as input the binding array generated by the semantic analysis
37 * i.e the evaluation context and the expression to evaluate given the context
38 *
39 * The expression to evaluate is often a call to a function, for a standard
40 * program that function will be main.
41 *
42 * While being slow this evaluator has the advantage or returning an AST
43 * as result which makes it perfect for compile-time usage.
44 *
45 * We can call the evaluator on standard declaration which will result in
46 * constant folding everything it can. Note that because we are able to represent
47 * complex types at compile time, string/list/dict even object operations can be folded.
48 *
49 * .. code-block:: python
50 *
51 * value = '.'.join(['a', 'b', 'c']) # <= Everything is known at compile time
52 * # It can be folded
53 *
54 * Additionally, this can be used to generate code at compile time by
55 * creating function with types as arguments.
56 *
57 * .. code-block::
58 *
59 * def Point(type: Type):
60 * class point:
61 * x: type
62 * y: type
63 *
64 * return point
65 *
66 * Pointi = Point(int)
67 * Pointf = Point(float) # <= Generate new types at compile time
68 *
69 *
70 * Evaluation Implementation
71 * -------------------------
72 *
73 * 1. Reuse as much as possible from the sema context
74 * Save the context inside each statement so we can use it
75 * during evaluation.
76 * Because the context is copied, it is easy to do parallel executions
77 *
78 * 2. Create a different context for evaluation only
79 */
80 struct TreeEvaluator: BaseVisitor<TreeEvaluator, false, TreeEvaluatorTrait> {
81
82 public:
83 using Super = BaseVisitor<TreeEvaluator, false, TreeEvaluatorTrait>;
84
85 TreeEvaluator(Bindings& bindings): bindings(bindings) { traces.push_back(StackTrace()); }
86
87 virtual ~TreeEvaluator() {}
88
89 template <typename Exception, typename... Args>
90 ConstantValue raise(Args... args) {
91 return ConstantValue::none();
92 }
93
94 #define FUNCTION_GEN(name, fun) virtual PartialResult* fun(name##_t* n, int depth);
95
96 #define X(name, _)
97 #define SSECTION(name)
98 #define MOD(name, fun)
99 #define EXPR(name, fun) FUNCTION_GEN(name, fun)
100 #define STMT(name, fun) FUNCTION_GEN(name, fun)
101 #define MATCH(name, fun) FUNCTION_GEN(name, fun)
102
103 NODEKIND_ENUM(X, SSECTION, EXPR, STMT, MOD, MATCH)
104
105 #undef X
106 #undef SSECTION
107 #undef EXPR
108 #undef STMT
109 #undef MOD
110 #undef MATCH
111
112 #undef FUNCTION_GEN
113
114 // this some clean up code, acknowledge we have exceptions
115 // but this code needs to run regardless, it will stop if new exceptions are raised
116 struct HandleException {
117 HandleException(TreeEvaluator* self): self(self) {
118 self->handling_exceptions = int(self->exceptions.size());
119 }
120
121 ~HandleException() { self->handling_exceptions = 0; }
122
123 TreeEvaluator* self;
124 };
125
126 // Helpers
127 PartialResult* get_next(Node* iterator, int depth);
128 PartialResult* call_enter(Node* ctx, int depth);
129 PartialResult* call_exit(Node* ctx, int depth);
130 PartialResult* call_native(Call_t* call, BuiltinType_t* n, int depth);
131 PartialResult* call_script(Call_t* call, FunctionDef_t* n, int depth);
132 PartialResult* call_constructor(Call_t* call, ClassDef_t* cls, int depth);
133 PartialResult* make_generator(Call_t* call, FunctionDef_t* n, int depth);
134
135 void raise_exception(PartialResult* exception, PartialResult* cause);
136
137 // Only returns true when new exceptions pop up
138 // we usually expect 0 exceptions,
139 // during exceptions handling we will execpt n and this will only be true
140 // if new exceptions are raised during the previous exceptions handling
141 bool has_exceptions() const { return int(exceptions.size()) > handling_exceptions; }
142
143 PartialResult* eval(StmtNode_t* stmt);
144
145 Constant* make(ClassDef* class_t, Array<Constant*> args, int depth);
146
147 private:
148 PartialResult* exec(StmtNode_t* stmt, int depth) {
149 176 StackTrace& trace = get_kwtrace();
150 176 trace.stmt = stmt;
151 176 return Super::exec(stmt, depth);
152 }
153
154 PartialResult* exec(ExprNode_t* expr, int depth) {
155 570 StackTrace& trace = get_kwtrace();
156 570 trace.expr = expr;
157 570 return Super::exec(expr, depth);
158 }
159
160 StackTrace& get_kwtrace() { return traces[traces.size() - 1]; }
161
162 // private:
163 // --------
164 public:
165 void set_return_value(PartialResult* ret) {
166 // I cant delete the return value here, it might be re-used in the context
167 // It is hard to decide when to delete the return value
168 // the problem lie when a value is returned, its scope ends
169 // but the value belongs to the upper scope
170 //
171 // Maybe just make a stack of scopes and return makes the value belong to the upper scope
172 // or promote it to the upper scope so it does not get deleted
173 //
174 // I thought about allocating the return value before the call is made
175 // so I do not have to promote the return value (it would already be on the right scope)
176 // but it might get tricky with values referenced twice
177 // when the variable is promoted the references are removed but not freed because it is
178 // still used as a return value
179
180 // if (return_value != nullptr) {
181 // root.remove_child_if_parent(return_value, true);
182 // }
183 47 return_value = ret;
184 47 }
185
186 // This can be used as a root for garbage collection
187 // root is never deleted but its children gets checked as reachable or not
188 // we can traverse the bindings struct to check if all values are reachable or not
189 // every time we leave a scope we could do a quick small GC step on that scope
190 // to remove free temporary variables and only keep the return value
191 Expression root;
192
193 Bindings& bindings;
194 PartialResult* return_value = nullptr;
195
196 private:
197 // `Registers`
198
199 bool loop_break = false;
200 bool loop_continue = false;
201 bool yielding = false;
202
203 PartialResult* cause = nullptr;
204 int handling_exceptions = 0;
205
206 Array<struct lyException*> exceptions;
207 Array<StackTrace> traces;
208 };
209
210 } // namespace lython
211
212 #endif
213