VapourSynth-llvmexpr
Loading...
Searching...
No Matches
SemanticAnalyzer.cpp
Go to the documentation of this file.
1
19
20#include "SemanticAnalyzer.hpp"
21#include "Builtins.hpp"
24#include <algorithm>
25#include <cstddef>
26#include <format>
27#include <functional>
28#include <string>
29
30namespace infix2postfix {
31
33 int num_intermediate_inputs,
34 int library_line_count)
35 : mode(mode), num_inputs(num_inputs),
36 num_intermediate_inputs(num_intermediate_inputs),
37 library_line_count(library_line_count),
38 global_scope(std::make_unique<SymbolTable>()),
39 current_scope(global_scope.get()) {}
40
41bool SemanticAnalyzer::analyze(const Program* program) {
42 if (program == nullptr) {
43 reportError("Program AST is null", Range{});
44 return false;
45 }
46
47 current_pending_gotos.clear();
48 current_labels_seen.clear();
49
50 // Collect global labels
51 for (const auto& stmt : program->statements) {
52 if (auto* label_def = get_if<LabelStmt>(stmt.get())) {
53 if (global_labels.contains(label_def->name.value)) {
54 reportError(std::format("Duplicate label '{}' in global scope",
55 label_def->name.value),
56 label_def->range);
57 }
58 global_labels.insert(label_def->name.value);
59 }
60 }
61
62 // Collect function signatures and build function symbols
63 for (const auto& stmt : program->statements) {
64 if (auto* func_def = get_if<FunctionDef>(stmt.get())) {
65 if (function_signatures.contains(func_def->name.value)) {
66 for (const auto& existing_sig :
67 function_signatures.at(func_def->name.value)) {
68 if (existing_sig.params.size() == func_def->params.size()) {
69 bool same = true;
70 for (size_t i = 0; i < func_def->params.size(); ++i) {
71 if (existing_sig.params[i].type !=
72 func_def->params[i].type) {
73 same = false;
74 break;
75 }
76 }
77 if (same) {
78 reportError(
79 std::format(
80 "Duplicate function signature for '{}'",
81 func_def->name.value),
82 func_def->range);
83 }
84 }
85 }
86 }
87
89 sig.name = func_def->name.value;
90 for (const auto& p : func_def->params) {
91 sig.params.push_back({p.name.value, p.type});
92 }
93 sig.range = func_def->range;
94
95 sig.has_return = false;
96 std::optional<bool> returns_value_opt;
97
98 std::function<void(Stmt*)> find_returns = [&](Stmt* s) {
99 if (!s) {
100 return;
101 }
102
103 if (auto* ret = get_if<ReturnStmt>(s)) {
104 sig.has_return = true;
105 bool current_has_value = (ret->value != nullptr);
106 if (!returns_value_opt.has_value()) {
107 returns_value_opt = current_has_value;
108 } else if (returns_value_opt.value() != current_has_value) {
109 reportError(
110 std::format(
111 "Function '{}' has inconsistent return "
112 "statements (some with values, some without).",
113 func_def->name.value),
114 ret->range);
115 }
116 } else if (auto* block = get_if<BlockStmt>(s)) {
117 for (const auto& inner_s : block->statements) {
118 find_returns(inner_s.get());
119 }
120 } else if (auto* if_s = get_if<IfStmt>(s)) {
121 find_returns(if_s->then_branch.get());
122 if (if_s->else_branch) {
123 find_returns(if_s->else_branch.get());
124 }
125 } else if (auto* while_s = get_if<WhileStmt>(s)) {
126 find_returns(while_s->body.get());
127 }
128 };
129 for (const auto& s : func_def->body->statements) {
130 find_returns(s.get());
131 }
132 sig.returns_value = returns_value_opt.value_or(false);
133
134 if (func_def->global_decl) {
135 sig.global_mode = func_def->global_decl->mode;
136 for (const auto& g : func_def->global_decl->globals) {
137 sig.specific_globals.insert(g.value);
138 }
139 }
140
141 std::set<std::string> local_vars;
142 for (const auto& p : func_def->params) {
143 local_vars.insert(p.name.value);
144 }
145
146 std::function<void(Stmt*)> collect_local_defs = [&](Stmt* s) {
147 if (!s) {
148 return;
149 }
150 if (auto* assign = get_if<AssignStmt>(s)) {
151 local_vars.insert(assign->name.value);
152 } else if (auto* block = get_if<BlockStmt>(s)) {
153 for (const auto& stmt : block->statements) {
154 collect_local_defs(stmt.get());
155 }
156 } else if (auto* if_stmt = get_if<IfStmt>(s)) {
157 collect_local_defs(if_stmt->then_branch.get());
158 if (if_stmt->else_branch) {
159 collect_local_defs(if_stmt->else_branch.get());
160 }
161 } else if (auto* while_stmt = get_if<WhileStmt>(s)) {
162 collect_local_defs(while_stmt->body.get());
163 }
164 };
165 for (const auto& s : func_def->body->statements) {
166 collect_local_defs(s.get());
167 }
168
169 for (const auto& s : func_def->body->statements) {
170 collectUsedGlobalsInStmt(s.get(), sig.used_globals);
171 }
172
173 for (const auto& local_var : local_vars) {
174 sig.used_globals.erase(local_var);
175 }
176
177 function_signatures[sig.name].push_back(sig);
178 function_defs[sig.name].push_back(func_def);
179
180 auto func_symbol = defineSymbol(SymbolKind::Function, sig.name,
181 Type::Value, sig.range);
182 func_symbol->signature = &function_signatures[sig.name].back();
183 func_def->symbol = func_symbol;
184 }
185 }
186
187 // Analyze all statements
188 for (const auto& stmt : program->statements) {
189 analyzeStmt(stmt.get());
190
191 validateGlobalDependencies(stmt.get());
192
193 if (auto* assign = get_if<AssignStmt>(stmt.get())) {
194 if (current_scope == global_scope.get()) {
195 defined_global_vars.insert(assign->name.value);
196 }
197 }
198 }
199
200 // Check for recursion in function call graph
201 std::set<std::string> visited;
202 for (const auto& [func_name, _] : function_call_graph) {
203 std::set<std::string> visiting;
204 std::vector<std::string> cycle_path;
205
206 if (detectCycleInCallGraph(func_name, visiting, visited, cycle_path)) {
207
208 std::string cycle_str;
209 for (size_t i = 0; i < cycle_path.size(); ++i) {
210 cycle_str += cycle_path[i];
211 if (i < cycle_path.size() - 1) {
212 cycle_str += " -> ";
213 }
214 }
215 cycle_str += " -> " + cycle_path[0];
216
217 Range first_range;
218 if (function_signatures.contains(cycle_path[0])) {
219 first_range = function_signatures.at(cycle_path[0])[0].range;
220 }
221
222 reportError(std::format("Recursion is not allowed: {}", cycle_str),
223 first_range);
224
225 for (const auto& fn : cycle_path) {
226 if (function_signatures.contains(fn)) {
227 const auto& sig = function_signatures.at(fn)[0];
228 reportError(
229 std::format(
230 " Function '{}' is part of the recursion cycle",
231 fn),
232 sig.range);
233 }
234 }
235
236 break;
237 }
238 }
239
240 // Check for unused global variables and functions
241 for (const auto& [name, symbol] : global_scope->getSymbols()) {
242 if (!symbol->is_used) {
243 if (symbol->name == "RESULT" || symbol->name == "_") {
244 continue;
245 }
246
247 // Skip warnings for symbols defined in standard library code
248 if (symbol->definition_range.start.line <= library_line_count) {
249 continue;
250 }
251
252 reportWarning(std::format("Unused symbol '{}'", name),
253 symbol->definition_range);
254 }
255 }
256
257 // Check if RESULT is defined in Expr/VkExpr mode
258 if ((mode == Mode::Expr || mode == Mode::VkExpr) && !has_result) {
259 reportError("Final result must be assigned to variable 'RESULT'!",
260 Range{});
261 }
262
263 if ((mode == Mode::Expr || mode == Mode::VkExpr) &&
264 !result_defined_in_global_scope) {
265 reportError(
266 "'RESULT' must be defined in the global scope in Expr mode.",
267 Range{});
268 }
269
270 return !hasErrors();
271}
272
274 return std::ranges::any_of(diagnostics, [](const auto& diag) {
275 return diag.severity == DiagnosticSeverity::Error;
276 });
277}
278
279void SemanticAnalyzer::reportError(const std::string& message,
280 const Range& range) {
281 diagnostics.emplace_back(DiagnosticSeverity::Error, message, range);
282}
283
284void SemanticAnalyzer::reportWarning(const std::string& message,
285 const Range& range) {
286 diagnostics.emplace_back(DiagnosticSeverity::Warning, message, range);
287}
288
289bool SemanticAnalyzer::detectCycleInCallGraph(
290 const std::string& func_name, std::set<std::string>& visiting,
291 std::set<std::string>& visited, std::vector<std::string>& cycle_path) {
292
293 if (visited.contains(func_name)) {
294 return false;
295 }
296
297 if (visiting.contains(func_name)) {
298 cycle_path.push_back(func_name);
299 return true;
300 }
301
302 visiting.insert(func_name);
303 cycle_path.push_back(func_name);
304
305 if (function_call_graph.contains(func_name)) {
306 for (const auto& callee : function_call_graph.at(func_name)) {
307 if (detectCycleInCallGraph(callee, visiting, visited, cycle_path)) {
308 if (cycle_path.front() == func_name) {
309 return true;
310 }
311 return true;
312 }
313 }
314 }
315
316 visiting.erase(func_name);
317 cycle_path.pop_back();
318 visited.insert(func_name);
319 return false;
320}
321
322// Symbol table management
323void SemanticAnalyzer::enterScope() {
324 auto new_scope = std::make_unique<SymbolTable>(current_scope);
325 current_scope = new_scope.get();
326 scope_stack.push_back(std::move(new_scope));
327}
328
329void SemanticAnalyzer::exitScope() {
330 if (!scope_stack.empty()) {
331 current_scope = current_scope->getParent();
332 scope_stack.pop_back();
333 }
334}
335
336std::shared_ptr<Symbol> SemanticAnalyzer::defineSymbol(SymbolKind kind,
337 const std::string& name,
338 Type type,
339 const Range& range) {
340 auto symbol = std::make_shared<Symbol>();
341 symbol->kind = kind;
342 symbol->name = name;
343 symbol->type = type;
344 symbol->definition_range = range;
345
346 if (!current_scope->define(symbol)) {
347 return current_scope->resolve(name);
348 }
349
350 return symbol;
351}
352
353std::shared_ptr<Symbol> SemanticAnalyzer::resolveSymbol(const std::string& name,
354 const Range& range) {
355 if (name == "_") {
356 reportError("The special variable '_' cannot be used in an expression. "
357 "It is only for discarding values.",
358 range);
359 }
360 auto symbol = current_scope->resolve(name);
361 if (!symbol) {
362 reportError(
363 std::format("Variable '{}' is used before being defined", name),
364 range);
365 } else {
366 symbol->is_used = true;
367 }
368 return symbol;
369}
370
371// Expression analysis
372Type SemanticAnalyzer::analyzeExpr(Expr* expr) {
373 if (expr == nullptr) {
374 return Type::Value;
375 }
376 return std::visit([this](auto& e) -> Type { return this->analyze(e); },
377 expr->value);
378}
379
380void SemanticAnalyzer::analyzeStmt(Stmt* stmt) {
381 if (stmt == nullptr) {
382 return;
383 }
384 std::visit([this](auto& s) { this->analyze(s); }, stmt->value);
385}
386
387Type SemanticAnalyzer::analyze([[maybe_unused]] const NumberExpr& expr) {
388 return Type::Literal;
389}
390
392 std::string name = expr.name.value;
393
394 if (name == "_") {
395 reportError("The special variable '_' cannot be used in an expression. "
396 "It is only for discarding values.",
397 expr.range);
398 }
399
400 if (name.starts_with("$")) {
401 std::string base_name = name.substr(1);
402
403 if (base_name == "X" || base_name == "Y") {
404 if (mode == Mode::Single) {
405 reportError(
406 "X and Y coordinates are only available in Expr mode. "
407 "SingleExpr processes the entire frame at once, not "
408 "pixel-by-pixel.",
409 expr.range);
410 }
411 return Type::Value;
412 }
413 if (auto buf_idx = get_buffer_index(base_name)) {
414 if (mode != Mode::VkExpr) {
415 reportError(
416 "Intermediate buffers are only available in VkExpr mode.",
417 expr.range);
418 return Type::Value;
419 }
420 if (*buf_idx < 0 || *buf_idx >= num_intermediate_inputs) {
421 reportError(
422 std::format("Buffer '{}' is out of range for this stage.",
423 base_name),
424 expr.range);
425 }
426 return Type::Clip;
427 }
428 if (auto clip_idx = get_clip_index(base_name)) {
429 if (*clip_idx > num_inputs - 1) {
430 reportError(
431 std::format("Clip index '{}' is out of range", base_name),
432 expr.range);
433 }
434 return Type::Clip;
435 }
436 if (base_name == "width" || base_name == "height") {
437 reportWarning(std::format("The built-in constant '${}' is "
438 "deprecated and will be removed "
439 "in a future version.",
440 base_name),
441 expr.range);
442 return Type::Value;
443 }
444 if (base_name == "pi" || base_name == "N") {
445 return Type::Value;
446 }
447 reportError(std::format("Invalid identifier '{}'.", base_name),
448 expr.range);
449 return Type::Value;
450 }
451
452 auto symbol = current_scope->resolve(name);
453
454 if (!symbol && (current_function != nullptr)) {
455 if (current_function->global_mode == GlobalMode::All) {
456 symbol = global_scope->resolve(name);
457 // Global declaration is forward declared
458 if (!symbol) {
459 symbol = std::make_shared<Symbol>();
460 symbol->kind = SymbolKind::Variable;
461 symbol->name = name;
462 symbol->type = Type::Value;
463 symbol->definition_range = expr.range;
464 }
465 } else if (current_function->global_mode == GlobalMode::Specific) {
466 // Global declaration is forward declared
467 if (current_function->specific_globals.contains(name)) {
468 symbol = global_scope->resolve(name);
469 if (!symbol) {
470 symbol = std::make_shared<Symbol>();
471 symbol->kind = SymbolKind::Variable;
472 symbol->name = name;
473 symbol->type = Type::Value;
474 symbol->definition_range = expr.range;
475 }
476 }
477 }
478 }
479
480 if (!symbol) {
481 reportError(
482 std::format("Variable '{}' is used before being defined", name),
483 expr.range);
484 return Type::Value;
485 }
486
487 symbol->is_used = true;
488 expr.symbol = symbol;
489 return symbol->type;
490}
491
493 if (expr.op.type == TokenType::Minus) {
494 if (get_if<NumberExpr>(expr.right.get()) != nullptr) {
495 return Type::Literal;
496 }
497 }
498
499 auto right_type = analyzeExpr(expr.right.get());
500 if (!is_convertible(right_type, Type::Value, mode)) {
501 reportError(std::format("Cannot apply unary operator '{}' to type "
502 "'{}' which is not convertible to a value.",
503 token_type_to_string(expr.op.type),
504 enum_name(right_type)),
505 expr.range);
506 }
507
508 return Type::Value;
509}
510
512 auto left_type = analyzeExpr(expr.left.get());
513 auto right_type = analyzeExpr(expr.right.get());
514
515 if (!is_convertible(left_type, Type::Value, mode)) {
516 reportError(std::format("Left operand of binary operator '{}' has type "
517 "'{}' which is not convertible to a value.",
518 token_type_to_string(expr.op.type),
519 enum_name(left_type)),
520 expr.range);
521 }
522 if (!is_convertible(right_type, Type::Value, mode)) {
523 reportError(
524 std::format("Right operand of binary operator '{}' has type "
525 "'{}' which is not convertible to a value.",
526 token_type_to_string(expr.op.type),
527 enum_name(right_type)),
528 expr.range);
529 }
530
531 return Type::Value;
532}
533
535 auto cond_type = analyzeExpr(expr.cond.get());
536 if (!is_convertible(cond_type, Type::Value, mode)) {
537 reportError(std::format("Ternary condition has type '{}' which is not "
538 "convertible to a value.",
539 enum_name(cond_type)),
540 expr.range);
541 }
542
543 auto true_type = analyzeExpr(expr.true_expr.get());
544 auto false_type = analyzeExpr(expr.false_expr.get());
545
546 if (!is_convertible(true_type, Type::Value, mode) ||
547 !is_convertible(false_type, Type::Value, mode)) {
548 reportError("Both branches of a ternary expression must be convertible "
549 "to a value.",
550 expr.range);
551 }
552
553 return Type::Value;
554}
555
557 const auto* signature =
558 resolveOverload(expr.callee, expr.args, expr.range, &expr);
559 expr.resolved_signature = signature;
560
561 if (signature != nullptr && !function_call_stack.empty()) {
562 const std::string& caller = function_call_stack.back();
563 function_call_graph[caller].insert(expr.callee);
564 }
565
566 if (expr.callee == "set_prop" || expr.callee == "set_propf" ||
567 expr.callee == "set_propi" || expr.callee == "set_propaf" ||
568 expr.callee == "set_propai" || expr.callee == "remove_prop") {
569
570 if (expr.resolved_builtin == nullptr) {
571 return Type::Void;
572 }
573
574 auto* prop_name_expr = get_if<VariableExpr>(expr.args[0].get());
575
576 std::string prop_name = prop_name_expr->name.value;
577 std::string suffix;
578 std::string_view callee_view(expr.callee);
579
580 if (callee_view == "remove_prop") {
581 suffix = "d";
582 } else {
583 suffix = callee_view.substr(std::string_view("set_prop").length());
584 if (suffix.empty()) {
585 suffix = "f";
586 }
587 }
588
589 auto it = written_properties.find(prop_name);
590 if (it != written_properties.end()) {
591 auto& stored_suffix = it->second.first;
592 if (stored_suffix != suffix) {
593 if (stored_suffix == "d") {
594 stored_suffix = suffix;
595 it->second.second = expr.range;
596 } else if (suffix != "d") {
597 reportError(
598 std::format("Property '{}' is written with "
599 "inconsistent types. First write was "
600 "'{}', current write is '{}'.",
601 prop_name, stored_suffix, suffix),
602 expr.range);
603 }
604 }
605 } else {
606 written_properties[prop_name] = {suffix, expr.range};
607 }
608 }
609
610 if (expr.resolved_signature != nullptr) {
611 return expr.resolved_signature->returns_value ? Type::Value
612 : Type::Void;
613 }
614 if (expr.resolved_builtin != nullptr) {
615 return expr.resolved_builtin->returns_value ? Type::Value : Type::Void;
616 }
617 if (expr.callee.starts_with("nth_")) {
618 return Type::Value;
619 }
620
621 return Type::Value;
622}
623
624const FunctionSignature* SemanticAnalyzer::resolveOverload(
625 const std::string& name, const std::vector<std::unique_ptr<Expr>>& args,
626 const Range& range, CallExpr* call_expr) {
627 // User-defined functions
628 if (function_signatures.contains(name)) {
629 const auto& overloads = function_signatures.at(name);
630
631 std::vector<Type> arg_types;
632 arg_types.reserve(args.size());
633 for (const auto& arg : args) {
634 if (auto* var_expr = get_if<VariableExpr>(arg.get())) {
635 auto symbol = current_scope->resolve(var_expr->name.value);
636 if (symbol && symbol->type == Type::Array) {
637 arg_types.push_back(Type::Array);
638 continue;
639 }
640 }
641 arg_types.push_back(analyzeExpr(arg.get()));
642 }
643
644 std::vector<const FunctionSignature*> possible_overloads;
645 for (const auto& s : overloads) {
646 if (s.params.size() == args.size()) {
647 possible_overloads.push_back(&s);
648 }
649 }
650
652 possible_overloads, args.size(),
653 [&](const FunctionSignature* /*sig*/,
654 size_t j) -> std::optional<Type> { return arg_types[j]; },
655 [&](const FunctionSignature* sig, size_t j) -> std::optional<Type> {
656 return sig->params[j].type;
657 },
658 mode);
659
660 if (candidates.empty()) {
661 std::string arg_types_str;
662 for (size_t i = 0; i < arg_types.size(); ++i) {
663 arg_types_str += std::string(enum_name(arg_types[i]));
664 if (i < arg_types.size() - 1) {
665 arg_types_str += ", ";
666 }
667 }
668 reportError(
669 std::format(
670 "No matching user-defined function for call to '{}({})'",
671 name, arg_types_str),
672 range);
673 return nullptr;
674 }
675
676 // Select best candidate
677 const auto* best_candidate = select_best_candidate(candidates);
678
679 if (is_ambiguous(candidates, best_candidate)) {
680 reportError(
681 std::format("Ambiguous call to overloaded function '{}'", name),
682 range);
683 }
684
685 // Find and set the resolved FunctionDef
686 if ((call_expr != nullptr) && (function_defs.contains(name))) {
687 const auto& defs = function_defs.at(name);
688 for (auto* def : defs) {
689 if (def->params.size() ==
690 (*best_candidate->item)->params.size()) {
691 bool match = true;
692 for (size_t i = 0; i < def->params.size(); ++i) {
693 if (def->params[i].type !=
694 (*best_candidate->item)->params[i].type) {
695 match = false;
696 break;
697 }
698 }
699 if (match) {
700 call_expr->resolved_def = def;
701 if (def->symbol) {
702 def->symbol->is_used = true;
703 }
704 break;
705 }
706 }
707 }
708 }
709
710 return *best_candidate->item;
711 }
712
713 const auto& builtins = get_builtin_functions();
714
715 if (builtins.contains(name)) {
716 const auto& overloads = builtins.at(name);
717
718 std::vector<const BuiltinFunction*> possible_overloads;
719 for (const auto& o : overloads) {
720 if (static_cast<size_t>(o.arity) == args.size() &&
721 (!o.mode_restriction.has_value() ||
722 o.mode_restriction.value() == mode ||
723 (o.mode_restriction == Mode::Expr && mode == Mode::VkExpr))) {
724 possible_overloads.push_back(&o);
725 }
726 }
727
728 std::vector<std::optional<Type>> arg_types(args.size());
729
731 possible_overloads, args.size(),
732 [&](const BuiltinFunction* builtin,
733 size_t j) -> std::optional<Type> {
734 Type param_type = builtin->param_types[j];
735 if (param_type == Type::LiteralString) {
736 auto* var_expr = get_if<VariableExpr>(args[j].get());
737 if (!var_expr || var_expr->name.value.starts_with("$")) {
738 return std::nullopt;
739 }
740 return Type::LiteralString;
741 }
742
743 if (!arg_types[j].has_value()) {
744 arg_types[j] = analyzeExpr(args[j].get());
745 }
746 return arg_types[j];
747 },
748 [&](const BuiltinFunction* builtin, size_t j)
749 -> std::optional<Type> { return builtin->param_types[j]; },
750 mode);
751
752 if (candidates.empty()) {
753 std::string arg_types_str;
754 for (size_t i = 0; i < args.size(); ++i) {
755 if (builtinParamTypeIsEvaluatable(overloads, i)) {
756 if (!arg_types[i].has_value()) {
757 arg_types[i] = analyzeExpr(args[i].get());
758 }
759 arg_types_str +=
760 std::string(enum_name(arg_types[i].value()));
761 } else {
762 arg_types_str += "LiteralString";
763 }
764
765 if (i < args.size() - 1) {
766 arg_types_str += ", ";
767 }
768 }
769 reportError(
770 std::format(
771 "No matching overload for function '{}({})' in {} mode.",
772 name, arg_types_str, enum_name(mode)),
773 range);
774 return nullptr;
775 }
776
777 // Select best candidate
778 const auto* best_candidate = select_best_candidate(candidates);
779
780 if (is_ambiguous(candidates, best_candidate)) {
781 reportError(
782 std::format(
783 "Ambiguous call to overloaded built-in function '{}'",
784 name),
785 range);
786 }
787
788 if (call_expr != nullptr) {
789 call_expr->resolved_builtin = *best_candidate->item;
790 }
791
792 // Built-in functions don't have FunctionSignature, return nullptr
793 return nullptr;
794 }
795 if (name.starts_with("nth_")) {
796 std::string n_str = name.substr(4);
797 if (n_str.empty() || !std::ranges::all_of(n_str, ::isdigit)) {
798 reportError(std::format("Invalid nth_N function name '{}'", name),
799 range);
800 return nullptr;
801 }
802 int n = std::stoi(n_str);
803 int arg_count = static_cast<int>(args.size());
804 if (arg_count < n) {
805 reportError(std::format("Function '{}' requires at least {} "
806 "arguments, but {} were provided.",
807 name, n, arg_count),
808 range);
809 }
810 if (n < 1) {
811 reportError(std::format("Invalid nth_N function name '{}'", name),
812 range);
813 }
814 for (const auto& arg : args) {
815 auto arg_type = analyzeExpr(arg.get());
816 if (!is_convertible(arg_type, Type::Value, mode)) {
817 reportError(
818 std::format("Argument to function '{}' has type '{}' which "
819 "is not convertible to Value.",
820 name, enum_name(arg_type)),
821 arg->range());
822 }
823 }
824
825 return nullptr;
826 }
827 reportError(std::format("Unknown function '{}'", name), range);
828 return nullptr;
829}
830
831void SemanticAnalyzer::getAllSymbols(
832 const SymbolTable* scope,
833 std::map<std::string, std::shared_ptr<Symbol>>& symbols) const {
834 if (scope == nullptr) {
835 return;
836 }
837 for (const auto& [name, symbol] : scope->getSymbols()) {
838 if (!symbols.contains(name)) {
839 symbols[name] = symbol;
840 }
841 }
842 getAllSymbols(scope->getParent(), symbols);
843}
844
845void SemanticAnalyzer::validateClipReference(const std::string& clip_name,
846 const Range& range,
847 bool allow_buffer) {
848 std::string name = clip_name;
849
850 if (name.starts_with("$")) {
851 name = name.substr(1);
852 if (auto buf_idx = get_buffer_index(name)) {
853 if (!allow_buffer) {
854 reportError(
855 "Intermediate buffers do not have frame properties.",
856 range);
857 return;
858 }
859 if (mode != Mode::VkExpr) {
860 reportError(
861 "Intermediate buffers are only available in VkExpr mode.",
862 range);
863 return;
864 }
865 if (*buf_idx < 0 || *buf_idx >= num_intermediate_inputs) {
866 reportError(
867 std::format("Buffer '{}' is out of range for this stage.",
868 name),
869 range);
870 }
871 return;
872 }
873 auto clip_idx_opt = get_clip_index(name);
874 if (!clip_idx_opt) {
875 reportError(std::format("Invalid clip identifier '{}'.", clip_name),
876 range);
877 } else if (*clip_idx_opt > num_inputs - 1) {
878 reportError(std::format("Clip index '{}' is out of range", name),
879 range);
880 }
881 } else {
882 // Check if it's a function parameter of Clip type
883 auto symbol = current_scope->resolve(name);
884 if (!symbol || symbol->type != Type::Clip) {
885 reportError(
886 std::format(
887 "Clip '{}' is not a clip constant or Clip parameter.",
888 clip_name),
889 range);
890 }
891 }
892}
893
894Type SemanticAnalyzer::analyze(const PropAccessExpr& expr) {
895 validateClipReference(expr.clip.value, expr.range, false);
896 return Type::Value;
897}
898
899Type SemanticAnalyzer::analyze(const StaticRelPixelAccessExpr& expr) {
900 if (mode == Mode::Single) {
901 reportError("Static relative pixel access (clip[x,y]) is only "
902 "available in Expr mode. "
903 "Use dyn(clip, x, y, plane) for absolute access in "
904 "SingleExpr mode.",
905 expr.range);
906 }
907
908 validateClipReference(expr.clip.value, expr.range);
909 return Type::Value;
910}
911
912Type SemanticAnalyzer::analyze(FrameDimensionExpr& expr) {
913 reportWarning("The 'frame.width[N]' and 'frame.height[N]' constructs are "
914 "deprecated and will be removed in a future version.",
915 expr.range);
916
917 if (mode == Mode::Expr || mode == Mode::VkExpr) {
918 reportError("frame.width[N] and frame.height[N] are only "
919 "available in SingleExpr mode. "
920 "Use width and height directly in Expr/VkExpr mode.",
921 expr.range);
922 }
923
924 auto plane_type = analyzeExpr(expr.plane_index_expr.get());
925 if (plane_type != Type::Literal) {
926 reportError("Plane index must be a literal constant.",
927 expr.plane_index_expr->range());
928 }
929
930 return Type::Value;
931}
932
933Type SemanticAnalyzer::analyze(ArrayAccessExpr& expr) {
934 auto* var_expr = get_if<VariableExpr>(expr.array.get());
935 if (var_expr == nullptr) {
936 reportError("Array access requires a variable as the array.",
937 expr.range);
938 return Type::Value;
939 }
940
941 std::string array_name = var_expr->name.value;
942 auto symbol = resolveSymbol(array_name, expr.range);
943
944 if (!symbol || symbol->type != Type::Array) {
945 reportError(std::format("Variable '{}' is not an array.", array_name),
946 expr.range);
947 }
948
949 expr.array_symbol = symbol;
950
951 auto index_type = analyzeExpr(expr.index.get());
952 if (!is_convertible(index_type, Type::Value, mode)) {
953 reportError("Array index must be convertible to a value.",
954 expr.index->range());
955 }
956
957 return Type::Value;
958}
959
960// Statement analysis
961void SemanticAnalyzer::analyze(const ExprStmt& stmt) {
962 Type expr_type = analyzeExpr(stmt.expr.get());
963 if (expr_type != Type::Void) {
964 reportError("Expression result is unused.", stmt.range);
965 }
966}
967
968void SemanticAnalyzer::analyze(AssignStmt& stmt) {
969 if (stmt.name.value == "RESULT") {
970 has_result = true;
971 if (mode == Mode::Expr || mode == Mode::VkExpr) {
972 if (current_function == nullptr && scope_stack.empty()) {
973 result_defined_in_global_scope = true;
974 }
975 }
976 }
977
978 // Check if this is a new() or resize() call for array allocation
979 if (auto* call_expr = get_if<CallExpr>(stmt.value.get())) {
980 if (call_expr->callee == "new" || call_expr->callee == "resize") {
981 if (call_expr->args.size() != 1) {
982 reportError(
983 std::format("{}() requires exactly 1 argument (size).",
984 call_expr->callee),
985 stmt.range);
986 return;
987 }
988
989 std::string var_name = stmt.name.value;
990 auto existing_symbol = current_scope->resolve(var_name);
991 bool is_already_array =
992 (existing_symbol && existing_symbol->type == Type::Array);
993
994 if (call_expr->callee == "resize") {
995 if (mode == Mode::Expr || mode == Mode::VkExpr) {
996 reportError(
997 "resize() is only available in SingleExpr mode.",
998 stmt.range);
999 }
1000 if (!is_already_array) {
1001 reportError(
1002 std::format(
1003 "Variable '{}' is undefined or not an array. ",
1004 var_name),
1005 stmt.range);
1006 }
1007 } else { // new()
1008 if (is_already_array) {
1009 reportError(
1010 std::format("Cannot reallocate array '{}'.", var_name),
1011 stmt.range);
1012 }
1013 }
1014
1015 if (mode == Mode::Expr || mode == Mode::VkExpr) {
1016 auto size_type = analyzeExpr(call_expr->args[0].get());
1017 if (size_type != Type::Literal) {
1018 reportError("Array size must be a literal constant.",
1019 stmt.range);
1020 }
1021 } else {
1022 auto size_type = analyzeExpr(call_expr->args[0].get());
1023 if (!is_convertible(size_type, Type::Value, mode)) {
1024 reportError("Array size must be convertible to a value.",
1025 stmt.range);
1026 }
1027 }
1028
1029 auto symbol = defineSymbol(SymbolKind::Variable, var_name,
1030 Type::Array, stmt.range);
1031 stmt.symbol = symbol;
1032
1033 return;
1034 }
1035 }
1036 auto value_type = analyzeExpr(stmt.value.get());
1037 if (!is_convertible(value_type, Type::Value, mode)) {
1038 reportError(std::format("Variable assignment value must be convertible "
1039 "to a value, got {}.",
1040 enum_name(value_type)),
1041 stmt.range);
1042 }
1043
1044 std::string var_name = stmt.name.value;
1045 auto existing_symbol = current_scope->resolve(var_name);
1046
1047 if (existing_symbol && existing_symbol->type == Type::Array) {
1048 reportError(
1049 std::format(
1050 "Variable '{}' is an array and cannot be reassigned to a "
1051 "non-array value.",
1052 var_name),
1053 stmt.range);
1054 }
1055
1056 auto symbol =
1057 defineSymbol(SymbolKind::Variable, var_name, Type::Value, stmt.range);
1058 stmt.symbol = symbol;
1059}
1060
1061void SemanticAnalyzer::analyze(ArrayAssignStmt& stmt) {
1062 auto* array_access = get_if<ArrayAccessExpr>(stmt.target.get());
1063 if (array_access == nullptr) {
1064 reportError("Array assignment target must be an array access.",
1065 stmt.range);
1066 return;
1067 }
1068
1069 analyzeExpr(stmt.target.get());
1070
1071 auto value_type = analyzeExpr(stmt.value.get());
1072 if (!is_convertible(value_type, Type::Value, mode)) {
1073 reportError("Array assignment value must be convertible to a value.",
1074 stmt.value->range());
1075 }
1076}
1077
1078void SemanticAnalyzer::analyze(const BlockStmt& stmt) {
1079 ScopeGuard scope_guard(this);
1080
1081 for (const auto& s : stmt.statements) {
1082 analyzeStmt(s.get());
1083 }
1084}
1085
1086void SemanticAnalyzer::analyze(IfStmt& stmt) {
1087 auto cond_type = analyzeExpr(stmt.condition.get());
1088 if (!is_convertible(cond_type, Type::Value, mode)) {
1089 reportError(std::format("If condition has type '{}' which is not "
1090 "convertible to a value.",
1091 enum_name(cond_type)),
1092 stmt.condition->range());
1093 }
1094
1095 analyzeStmt(stmt.then_branch.get());
1096
1097 if (stmt.else_branch) {
1098 analyzeStmt(stmt.else_branch.get());
1099 }
1100}
1101
1102void SemanticAnalyzer::analyze(WhileStmt& stmt) {
1103 auto cond_type = analyzeExpr(stmt.condition.get());
1104 if (!is_convertible(cond_type, Type::Value, mode)) {
1105 reportError(std::format("While condition has type '{}' which is not "
1106 "convertible to a value.",
1107 enum_name(cond_type)),
1108 stmt.condition->range());
1109 }
1110
1111 analyzeStmt(stmt.body.get());
1112}
1113
1114void SemanticAnalyzer::analyze(const ReturnStmt& stmt) {
1115 if (current_function == nullptr) {
1116 reportError("'return' statements are not allowed in the global scope.",
1117 stmt.range);
1118 }
1119 if (stmt.value) {
1120 auto result_type = analyzeExpr(stmt.value.get());
1121 if (result_type == Type::Array) {
1122 reportError("Functions cannot return arrays.", stmt.range);
1123 }
1124 }
1125}
1126
1127void SemanticAnalyzer::analyze(LabelStmt& stmt) {
1128 auto symbol = defineSymbol(SymbolKind::Label, stmt.name.value, Type::Value,
1129 stmt.range);
1130 stmt.symbol = symbol;
1131
1132 current_labels_seen.insert(stmt.name.value);
1133
1134 if (current_pending_gotos.contains(stmt.name.value)) {
1135 symbol->is_used = true;
1136
1137 std::map<std::string, std::shared_ptr<Symbol>> symbols_at_label;
1138 getAllSymbols(current_scope, symbols_at_label);
1139
1140 const auto& pending_gotos = current_pending_gotos.at(stmt.name.value);
1141 for (const auto& goto_info : pending_gotos) {
1142 for (const auto& [name, symbol_at_label] : symbols_at_label) {
1143
1144 if (symbol_at_label->kind == SymbolKind::Variable &&
1145 !goto_info.symbols_at_goto.contains(name)) {
1146 reportError(
1147 std::format(
1148 "goto jumps over initialization of variable '{}'",
1149 name),
1150 goto_info.stmt->range);
1151 }
1152 }
1153 }
1154 current_pending_gotos.erase(stmt.name.value);
1155 }
1156}
1157
1158void SemanticAnalyzer::analyze(GotoStmt& stmt) {
1159 if (current_function != nullptr) {
1160 // Inside a function
1161 if (global_labels.contains(stmt.label.value)) {
1162 reportError(std::format("goto from function '{}' to global "
1163 "label '{}' is not allowed",
1164 current_function->name, stmt.label.value),
1165 stmt.range);
1166 }
1167 if (!current_function_labels.contains(stmt.label.value)) {
1168 reportError(
1169 std::format("goto target '{}' not found in function '{}'",
1170 stmt.label.value, current_function->name),
1171 stmt.range);
1172 }
1173 } else {
1174 // Global scope
1175 if (!global_labels.contains(stmt.label.value)) {
1176 reportError(
1177 std::format("goto target '{}' not found in global scope",
1178 stmt.label.value),
1179 stmt.range);
1180 }
1181 }
1182
1183 if (!current_labels_seen.contains(stmt.label.value)) {
1184 // Forward jump
1185 ForwardGotoInfo info;
1186 info.stmt = &stmt;
1187 getAllSymbols(current_scope, info.symbols_at_goto);
1188 current_pending_gotos[stmt.label.value].push_back(info);
1189 }
1190
1191 auto symbol = current_scope->resolve(stmt.label.value);
1192 if (symbol) {
1193 symbol->is_used = true;
1194 }
1195 stmt.target_label_symbol = symbol;
1196
1197 if (stmt.condition) {
1198 analyzeExpr(stmt.condition.get());
1199 }
1200}
1201
1202void SemanticAnalyzer::analyze(FunctionDef& stmt) {
1203 // Save global goto state and clear for function analysis
1204 auto saved_pending_gotos = std::move(current_pending_gotos);
1205 current_pending_gotos.clear();
1206 auto saved_labels_seen = std::move(current_labels_seen);
1207 current_labels_seen.clear();
1208
1209 // Collect labels in function
1210 auto saved_current_function_labels = current_function_labels;
1211 current_function_labels.clear();
1212 for (const auto& s : stmt.body->statements) {
1213 collectLabels(s.get(), current_function_labels, stmt.name.value,
1214 stmt.range);
1215 }
1216
1217 // Find the function signature
1218 const FunctionSignature* sig = nullptr;
1219 if (function_signatures.contains(stmt.name.value)) {
1220 for (const auto& s : function_signatures.at(stmt.name.value)) {
1221 if (s.params.size() == stmt.params.size()) {
1222 bool match = true;
1223 for (size_t i = 0; i < s.params.size(); ++i) {
1224 if (s.params[i].type != stmt.params[i].type) {
1225 match = false;
1226 break;
1227 }
1228 }
1229 if (match) {
1230 sig = &s;
1231 break;
1232 }
1233 }
1234 }
1235 }
1236
1237 if (sig == nullptr) {
1238 reportError(std::format("Could not find signature for function '{}'",
1239 stmt.name.value),
1240 stmt.range);
1241 current_function_labels = saved_current_function_labels;
1242 return;
1243 }
1244
1245 if (sig->has_return && sig->returns_value) {
1246 bool body_always_returns = false;
1247 body_always_returns =
1248 std::ranges::any_of(stmt.body->statements, [this](const auto& s) {
1249 return pathAlwaysReturns(s.get());
1250 });
1251 if (!body_always_returns) {
1252 reportError(
1253 std::format(
1254 "Not all control paths in function '{}' return a value.",
1255 stmt.name.value),
1256 stmt.range);
1257 }
1258 }
1259
1260 // Analyze function body with proper scope
1261 const auto* saved_current_function = current_function;
1262 current_function = sig;
1263
1264 // Push function onto call stack to detect recursion
1265 function_call_stack.push_back(stmt.name.value);
1266
1267 {
1268 ScopeGuard scope_guard(this);
1269
1270 // Add parameters
1271 for (const auto& param : stmt.params) {
1272 defineSymbol(SymbolKind::Parameter, param.name.value, param.type,
1273 stmt.range);
1274 }
1275
1276 // Analyze function body
1277 analyze(*stmt.body);
1278
1279 // Check for unused local variables and parameters
1280 for (const auto& [name, symbol] : current_scope->getSymbols()) {
1281 if (!symbol->is_used) {
1282 if (symbol->name == "_") {
1283 continue;
1284 }
1285 // Skip warnings for symbols in standard library functions
1286 if (symbol->definition_range.start.line <= library_line_count) {
1287 continue;
1288 }
1289 reportWarning(std::format("Unused symbol '{}'", name),
1290 symbol->definition_range);
1291 }
1292 }
1293 }
1294
1295 // Pop function from call stack
1296 function_call_stack.pop_back();
1297
1298 // Restore context
1299 current_function = saved_current_function;
1300 current_function_labels = saved_current_function_labels;
1301
1302 // Restore global goto state
1303 current_pending_gotos = std::move(saved_pending_gotos);
1304 current_labels_seen = std::move(saved_labels_seen);
1305}
1306
1307void SemanticAnalyzer::analyze([[maybe_unused]] const GlobalDecl& stmt) {
1308 // Global declarations are handled by the parser
1309}
1310
1311void SemanticAnalyzer::collectLabels(Stmt* stmt, std::set<std::string>& labels,
1312 const std::string& context,
1313 const Range& context_range) {
1314 if (stmt == nullptr) {
1315 return;
1316 }
1317 if (auto* label = get_if<LabelStmt>(stmt)) {
1318 if (labels.contains(label->name.value)) {
1319 reportError(std::format("Duplicate label '{}' in function '{}'",
1320 label->name.value, context),
1321 label->range);
1322 }
1323 labels.insert(label->name.value);
1324 } else if (auto* block = get_if<BlockStmt>(stmt)) {
1325 for (auto& inner_s : block->statements) {
1326 collectLabels(inner_s.get(), labels, context, context_range);
1327 }
1328 } else if (auto* if_s = get_if<IfStmt>(stmt)) {
1329 collectLabels(if_s->then_branch.get(), labels, context, context_range);
1330 if (if_s->else_branch) {
1331 collectLabels(if_s->else_branch.get(), labels, context,
1332 context_range);
1333 }
1334 } else if (auto* while_s = get_if<WhileStmt>(stmt)) {
1335 collectLabels(while_s->body.get(), labels, context, context_range);
1336 }
1337}
1338
1339bool SemanticAnalyzer::pathAlwaysReturns(Stmt* stmt) {
1340 if (stmt == nullptr) {
1341 return false;
1342 }
1343
1344 if (get_if<ReturnStmt>(stmt) != nullptr) {
1345 return true;
1346 }
1347 if (auto* if_s = get_if<IfStmt>(stmt)) {
1348 return pathAlwaysReturns(if_s->then_branch.get()) &&
1349 (if_s->else_branch &&
1350 pathAlwaysReturns(if_s->else_branch.get()));
1351 }
1352 if (auto* block = get_if<BlockStmt>(stmt)) {
1353 return std::ranges::any_of(block->statements, [this](const auto& s) {
1354 return pathAlwaysReturns(s.get());
1355 });
1356 }
1357 return false;
1358}
1359
1360bool SemanticAnalyzer::builtinParamTypeIsEvaluatable(
1361 const std::vector<BuiltinFunction>& overloads, size_t param_idx) {
1362 return std::ranges::all_of(overloads, [param_idx](const auto& o) {
1363 return !(o.param_types.size() > param_idx &&
1364 o.param_types[param_idx] == Type::LiteralString);
1365 });
1366}
1367
1368void SemanticAnalyzer::validateGlobalDependencies(Stmt* stmt) {
1369 if (stmt == nullptr) {
1370 return;
1371 }
1372
1373 std::function<void(Expr*)> check_expr = [&](Expr* expr) {
1374 if (!expr) {
1375 return;
1376 }
1377 if (auto* call = get_if<CallExpr>(expr)) {
1378 validateFunctionCall(*call);
1379 } else if (auto* binary = get_if<BinaryExpr>(expr)) {
1380 check_expr(binary->left.get());
1381 check_expr(binary->right.get());
1382 } else if (auto* unary = get_if<UnaryExpr>(expr)) {
1383 check_expr(unary->right.get());
1384 } else if (auto* ternary = get_if<TernaryExpr>(expr)) {
1385 check_expr(ternary->cond.get());
1386 check_expr(ternary->true_expr.get());
1387 check_expr(ternary->false_expr.get());
1388 }
1389 };
1390
1391 if (auto* expr_stmt = get_if<ExprStmt>(stmt)) {
1392 check_expr(expr_stmt->expr.get());
1393 } else if (auto* assign_stmt = get_if<AssignStmt>(stmt)) {
1394 check_expr(assign_stmt->value.get());
1395 } else if (auto* if_stmt = get_if<IfStmt>(stmt)) {
1396 check_expr(if_stmt->condition.get());
1397 validateGlobalDependencies(if_stmt->then_branch.get());
1398 if (if_stmt->else_branch) {
1399 validateGlobalDependencies(if_stmt->else_branch.get());
1400 }
1401 } else if (auto* while_stmt = get_if<WhileStmt>(stmt)) {
1402 check_expr(while_stmt->condition.get());
1403 validateGlobalDependencies(while_stmt->body.get());
1404 } else if (auto* block = get_if<BlockStmt>(stmt)) {
1405 for (const auto& s : block->statements) {
1406 validateGlobalDependencies(s.get());
1407 }
1408 }
1409}
1410
1411void SemanticAnalyzer::validateFunctionCall(const CallExpr& expr) {
1412 if (!function_signatures.contains(expr.callee)) {
1413 return;
1414 }
1415
1416 const FunctionSignature* sig = expr.resolved_signature;
1417 if (sig == nullptr) {
1418 auto it =
1419 std::ranges::find_if(function_signatures.at(expr.callee),
1420 [&](const FunctionSignature& s) {
1421 return s.params.size() == expr.args.size();
1422 });
1423 if (it != function_signatures.at(expr.callee).end()) {
1424 sig = &*it;
1425 }
1426 }
1427
1428 if (sig == nullptr) {
1429 return;
1430 }
1431
1432 if (sig->global_mode == GlobalMode::All) {
1433 for (const auto& global_name : sig->used_globals) {
1434 if (global_name.starts_with("$")) {
1435 reportError(
1436 std::format("Invalid global variable name {}", global_name),
1437 expr.range);
1438 }
1439
1440 if (!defined_global_vars.contains(global_name)) {
1441 reportError(
1442 std::format(
1443 "Function '{}' uses global variable '{}' which is "
1444 "not defined in global scope before this call",
1445 expr.callee, global_name),
1446 expr.range);
1447 }
1448 }
1449 } else if (sig->global_mode == GlobalMode::Specific) {
1450 for (const auto& global_name : sig->specific_globals) {
1451 if (!defined_global_vars.contains(global_name)) {
1452 reportError(
1453 std::format(
1454 "Function '{}' requires global variable '{}' which is "
1455 "not defined in global scope before this call",
1456 expr.callee, global_name),
1457 expr.range);
1458 }
1459 }
1460 }
1461}
1462
1463void SemanticAnalyzer::collectUsedGlobals(Expr* expr,
1464 std::set<std::string>& used_globals) {
1465 if (expr == nullptr) {
1466 return;
1467 }
1468
1469 if (auto* var_expr = get_if<VariableExpr>(expr)) {
1470 std::string name = var_expr->name.value;
1471 if (!name.starts_with("$") && name != "frame") {
1472 used_globals.insert(name);
1473 }
1474 } else if (auto* binary = get_if<BinaryExpr>(expr)) {
1475 collectUsedGlobals(binary->left.get(), used_globals);
1476 collectUsedGlobals(binary->right.get(), used_globals);
1477 } else if (auto* unary = get_if<UnaryExpr>(expr)) {
1478 collectUsedGlobals(unary->right.get(), used_globals);
1479 } else if (auto* ternary = get_if<TernaryExpr>(expr)) {
1480 collectUsedGlobals(ternary->cond.get(), used_globals);
1481 collectUsedGlobals(ternary->true_expr.get(), used_globals);
1482 collectUsedGlobals(ternary->false_expr.get(), used_globals);
1483 } else if (auto* call = get_if<CallExpr>(expr)) {
1484 for (const auto& arg : call->args) {
1485 collectUsedGlobals(arg.get(), used_globals);
1486 }
1487 } else if (auto* array_access = get_if<ArrayAccessExpr>(expr)) {
1488 collectUsedGlobals(array_access->array.get(), used_globals);
1489 collectUsedGlobals(array_access->index.get(), used_globals);
1490 } else if (auto* frame_dim = get_if<FrameDimensionExpr>(expr)) {
1491 collectUsedGlobals(frame_dim->plane_index_expr.get(), used_globals);
1492 }
1493}
1494
1495void SemanticAnalyzer::collectUsedGlobalsInStmt(
1496 Stmt* stmt, std::set<std::string>& used_globals) {
1497 if (stmt == nullptr) {
1498 return;
1499 }
1500
1501 if (auto* expr_stmt = get_if<ExprStmt>(stmt)) {
1502 collectUsedGlobals(expr_stmt->expr.get(), used_globals);
1503 } else if (auto* assign_stmt = get_if<AssignStmt>(stmt)) {
1504 collectUsedGlobals(assign_stmt->value.get(), used_globals);
1505 } else if (auto* array_assign = get_if<ArrayAssignStmt>(stmt)) {
1506 collectUsedGlobals(array_assign->target.get(), used_globals);
1507 collectUsedGlobals(array_assign->value.get(), used_globals);
1508 } else if (auto* if_stmt = get_if<IfStmt>(stmt)) {
1509 collectUsedGlobals(if_stmt->condition.get(), used_globals);
1510 collectUsedGlobalsInStmt(if_stmt->then_branch.get(), used_globals);
1511 if (if_stmt->else_branch) {
1512 collectUsedGlobalsInStmt(if_stmt->else_branch.get(), used_globals);
1513 }
1514 } else if (auto* while_stmt = get_if<WhileStmt>(stmt)) {
1515 collectUsedGlobals(while_stmt->condition.get(), used_globals);
1516 collectUsedGlobalsInStmt(while_stmt->body.get(), used_globals);
1517 } else if (auto* return_stmt = get_if<ReturnStmt>(stmt)) {
1518 if (return_stmt->value) {
1519 collectUsedGlobals(return_stmt->value.get(), used_globals);
1520 }
1521 } else if (auto* goto_stmt = get_if<GotoStmt>(stmt)) {
1522 if (goto_stmt->condition) {
1523 collectUsedGlobals(goto_stmt->condition.get(), used_globals);
1524 }
1525 } else if (auto* block = get_if<BlockStmt>(stmt)) {
1526 for (const auto& s : block->statements) {
1527 collectUsedGlobalsInStmt(s.get(), used_globals);
1528 }
1529 }
1530}
1531
1532} // namespace infix2postfix
consteval std::string_view enum_name()
Definition EnumName.hpp:57
bool analyze(const Program *program)
SemanticAnalyzer(Mode mode, int num_inputs, int num_intermediate_inputs=0, int library_line_count=0)
std::optional< int > get_buffer_index(const std::string &s)
Definition types.hpp:267
auto get_if(Wrapper *wrapper) -> decltype(std::get_if< T >(&wrapper->value))
Definition AST.hpp:442
bool is_ambiguous(const std::vector< OverloadCandidate< T > > &candidates, const OverloadCandidate< T > *best)
std::vector< OverloadCandidate< T > > compute_candidates(const std::vector< T > &overloads, size_t arg_count, ArgTypeGetter get_arg_type, ParamTypeGetter get_param_type, Mode mode)
bool is_convertible(Type from, Type to, Mode mode)
const std::map< std::string, std::vector< BuiltinFunction > > & get_builtin_functions()
Definition Builtins.cpp:400
const OverloadCandidate< T > * select_best_candidate(std::vector< OverloadCandidate< T > > &candidates)
std::string token_type_to_string(TokenType type)
Definition types.hpp:171
std::optional< int > get_clip_index(const std::string &s)
Definition types.hpp:256
std::set< std::string > specific_globals
Definition types.hpp:221
std::set< std::string > used_globals
Definition types.hpp:224
std::vector< ParameterInfo > params
Definition types.hpp:216
std::vector< std::unique_ptr< Stmt > > statements
Definition AST.hpp:415