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),
39 current_scope(global_scope.get()) {}
42 if (program ==
nullptr) {
43 reportError(
"Program AST is null",
Range{});
47 current_pending_gotos.clear();
48 current_labels_seen.clear();
53 if (global_labels.contains(label_def->name.value)) {
54 reportError(std::format(
"Duplicate label '{}' in global scope",
55 label_def->name.value),
58 global_labels.insert(label_def->name.value);
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()) {
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) {
80 "Duplicate function signature for '{}'",
81 func_def->name.value),
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});
93 sig.
range = func_def->range;
96 std::optional<bool> returns_value_opt;
98 std::function<void(
Stmt*)> find_returns = [&](
Stmt* s) {
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) {
111 "Function '{}' has inconsistent return "
112 "statements (some with values, some without).",
113 func_def->name.value),
117 for (
const auto& inner_s : block->statements) {
118 find_returns(inner_s.get());
121 find_returns(if_s->then_branch.get());
122 if (if_s->else_branch) {
123 find_returns(if_s->else_branch.get());
126 find_returns(while_s->body.get());
129 for (
const auto& s : func_def->body->statements) {
130 find_returns(s.get());
134 if (func_def->global_decl) {
136 for (
const auto& g : func_def->global_decl->globals) {
141 std::set<std::string> local_vars;
142 for (
const auto& p : func_def->params) {
143 local_vars.insert(p.name.value);
146 std::function<void(
Stmt*)> collect_local_defs = [&](
Stmt* s) {
151 local_vars.insert(assign->name.value);
153 for (
const auto& stmt : block->statements) {
154 collect_local_defs(stmt.get());
157 collect_local_defs(if_stmt->then_branch.get());
158 if (if_stmt->else_branch) {
159 collect_local_defs(if_stmt->else_branch.get());
162 collect_local_defs(while_stmt->body.get());
165 for (
const auto& s : func_def->body->statements) {
166 collect_local_defs(s.get());
169 for (
const auto& s : func_def->body->statements) {
173 for (
const auto& local_var : local_vars) {
177 function_signatures[sig.
name].push_back(sig);
178 function_defs[sig.
name].push_back(func_def);
182 func_symbol->signature = &function_signatures[sig.
name].back();
183 func_def->symbol = func_symbol;
188 for (
const auto& stmt : program->
statements) {
189 analyzeStmt(stmt.get());
191 validateGlobalDependencies(stmt.get());
194 if (current_scope == global_scope.get()) {
195 defined_global_vars.insert(assign->name.value);
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;
206 if (detectCycleInCallGraph(func_name, visiting, visited, cycle_path)) {
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) {
215 cycle_str +=
" -> " + cycle_path[0];
218 if (function_signatures.contains(cycle_path[0])) {
219 first_range = function_signatures.at(cycle_path[0])[0].range;
222 reportError(std::format(
"Recursion is not allowed: {}", cycle_str),
225 for (
const auto& fn : cycle_path) {
226 if (function_signatures.contains(fn)) {
227 const auto& sig = function_signatures.at(fn)[0];
230 " Function '{}' is part of the recursion cycle",
241 for (
const auto& [name, symbol] : global_scope->getSymbols()) {
242 if (!symbol->is_used) {
243 if (symbol->name ==
"RESULT" || symbol->name ==
"_") {
248 if (symbol->definition_range.start.line <= library_line_count) {
252 reportWarning(std::format(
"Unused symbol '{}'", name),
253 symbol->definition_range);
259 reportError(
"Final result must be assigned to variable 'RESULT'!",
264 !result_defined_in_global_scope) {
266 "'RESULT' must be defined in the global scope in Expr mode.",
274 return std::ranges::any_of(diagnostics, [](
const auto& diag) {
279void SemanticAnalyzer::reportError(
const std::string& message,
280 const Range& range) {
284void SemanticAnalyzer::reportWarning(
const std::string& message,
285 const Range& range) {
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) {
293 if (visited.contains(func_name)) {
297 if (visiting.contains(func_name)) {
298 cycle_path.push_back(func_name);
302 visiting.insert(func_name);
303 cycle_path.push_back(func_name);
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) {
316 visiting.erase(func_name);
317 cycle_path.pop_back();
318 visited.insert(func_name);
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));
329void SemanticAnalyzer::exitScope() {
330 if (!scope_stack.empty()) {
331 current_scope = current_scope->getParent();
332 scope_stack.pop_back();
336std::shared_ptr<Symbol> SemanticAnalyzer::defineSymbol(
SymbolKind kind,
337 const std::string& name,
339 const Range& range) {
340 auto symbol = std::make_shared<Symbol>();
344 symbol->definition_range = range;
346 if (!current_scope->define(symbol)) {
347 return current_scope->resolve(name);
353std::shared_ptr<Symbol> SemanticAnalyzer::resolveSymbol(
const std::string& name,
354 const Range& range) {
356 reportError(
"The special variable '_' cannot be used in an expression. "
357 "It is only for discarding values.",
360 auto symbol = current_scope->resolve(name);
363 std::format(
"Variable '{}' is used before being defined", name),
366 symbol->is_used =
true;
372Type SemanticAnalyzer::analyzeExpr(
Expr* expr) {
373 if (expr ==
nullptr) {
376 return std::visit([
this](
auto& e) ->
Type {
return this->
analyze(e); },
380void SemanticAnalyzer::analyzeStmt(
Stmt* stmt) {
381 if (stmt ==
nullptr) {
384 std::visit([
this](
auto& s) { this->
analyze(s); }, stmt->value);
392 std::string name = expr.name.value;
395 reportError(
"The special variable '_' cannot be used in an expression. "
396 "It is only for discarding values.",
400 if (name.starts_with(
"$")) {
401 std::string base_name = name.substr(1);
403 if (base_name ==
"X" || base_name ==
"Y") {
406 "X and Y coordinates are only available in Expr mode. "
407 "SingleExpr processes the entire frame at once, not "
416 "Intermediate buffers are only available in VkExpr mode.",
420 if (*buf_idx < 0 || *buf_idx >= num_intermediate_inputs) {
422 std::format(
"Buffer '{}' is out of range for this stage.",
429 if (*clip_idx > num_inputs - 1) {
431 std::format(
"Clip index '{}' is out of range", base_name),
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.",
444 if (base_name ==
"pi" || base_name ==
"N") {
447 reportError(std::format(
"Invalid identifier '{}'.", base_name),
452 auto symbol = current_scope->resolve(name);
454 if (!symbol && (current_function !=
nullptr)) {
456 symbol = global_scope->resolve(name);
459 symbol = std::make_shared<Symbol>();
463 symbol->definition_range = expr.range;
467 if (current_function->specific_globals.contains(name)) {
468 symbol = global_scope->resolve(name);
470 symbol = std::make_shared<Symbol>();
474 symbol->definition_range = expr.range;
482 std::format(
"Variable '{}' is used before being defined", name),
487 symbol->is_used =
true;
488 expr.symbol = symbol;
493 if (expr.op.type == TokenType::Minus) {
499 auto right_type = analyzeExpr(expr.right.get());
501 reportError(std::format(
"Cannot apply unary operator '{}' to type "
502 "'{}' which is not convertible to a value.",
512 auto left_type = analyzeExpr(expr.left.get());
513 auto right_type = analyzeExpr(expr.right.get());
516 reportError(std::format(
"Left operand of binary operator '{}' has type "
517 "'{}' which is not convertible to a value.",
524 std::format(
"Right operand of binary operator '{}' has type "
525 "'{}' which is not convertible to a value.",
535 auto cond_type = analyzeExpr(expr.cond.get());
537 reportError(std::format(
"Ternary condition has type '{}' which is not "
538 "convertible to a value.",
543 auto true_type = analyzeExpr(expr.true_expr.get());
544 auto false_type = analyzeExpr(expr.false_expr.get());
548 reportError(
"Both branches of a ternary expression must be convertible "
557 const auto* signature =
558 resolveOverload(expr.callee, expr.args, expr.range, &expr);
559 expr.resolved_signature = signature;
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);
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") {
570 if (expr.resolved_builtin ==
nullptr) {
576 std::string prop_name = prop_name_expr->name.value;
578 std::string_view callee_view(expr.callee);
580 if (callee_view ==
"remove_prop") {
583 suffix = callee_view.substr(std::string_view(
"set_prop").length());
584 if (suffix.empty()) {
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") {
598 std::format(
"Property '{}' is written with "
599 "inconsistent types. First write was "
600 "'{}', current write is '{}'.",
601 prop_name, stored_suffix, suffix),
606 written_properties[prop_name] = {suffix, expr.range};
610 if (expr.resolved_signature !=
nullptr) {
611 return expr.resolved_signature->returns_value ?
Type::Value
614 if (expr.resolved_builtin !=
nullptr) {
617 if (expr.callee.starts_with(
"nth_")) {
625 const std::string& name,
const std::vector<std::unique_ptr<Expr>>& args,
628 if (function_signatures.contains(name)) {
629 const auto& overloads = function_signatures.at(name);
631 std::vector<Type> arg_types;
632 arg_types.reserve(args.size());
633 for (
const auto& arg : args) {
635 auto symbol = current_scope->resolve(var_expr->name.value);
641 arg_types.push_back(analyzeExpr(arg.get()));
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);
652 possible_overloads, args.size(),
653 [&](
const FunctionSignature* ,
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;
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 +=
", ";
670 "No matching user-defined function for call to '{}({})'",
671 name, arg_types_str),
681 std::format(
"Ambiguous call to overloaded function '{}'", name),
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()) {
692 for (
size_t i = 0; i < def->params.size(); ++i) {
693 if (def->params[i].type !=
694 (*best_candidate->item)->params[i].type) {
700 call_expr->resolved_def = def;
702 def->symbol->is_used =
true;
710 return *best_candidate->item;
715 if (builtins.contains(name)) {
716 const auto& overloads = builtins.at(name);
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 ||
724 possible_overloads.push_back(&o);
728 std::vector<std::optional<Type>> arg_types(args.size());
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(
"$")) {
740 return Type::LiteralString;
743 if (!arg_types[j].has_value()) {
744 arg_types[j] = analyzeExpr(args[j].get());
748 [&](
const BuiltinFunction* builtin,
size_t j)
749 -> std::optional<Type> {
return builtin->param_types[j]; },
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());
760 std::string(
enum_name(arg_types[i].value()));
762 arg_types_str +=
"LiteralString";
765 if (i < args.size() - 1) {
766 arg_types_str +=
", ";
771 "No matching overload for function '{}({})' in {} mode.",
783 "Ambiguous call to overloaded built-in function '{}'",
788 if (call_expr !=
nullptr) {
789 call_expr->resolved_builtin = *best_candidate->item;
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),
802 int n = std::stoi(n_str);
803 int arg_count =
static_cast<int>(args.size());
805 reportError(std::format(
"Function '{}' requires at least {} "
806 "arguments, but {} were provided.",
811 reportError(std::format(
"Invalid nth_N function name '{}'", name),
814 for (
const auto& arg : args) {
815 auto arg_type = analyzeExpr(arg.get());
818 std::format(
"Argument to function '{}' has type '{}' which "
819 "is not convertible to Value.",
827 reportError(std::format(
"Unknown function '{}'", name), range);
831void SemanticAnalyzer::getAllSymbols(
832 const SymbolTable* scope,
833 std::map<std::string, std::shared_ptr<Symbol>>& symbols)
const {
834 if (scope ==
nullptr) {
837 for (
const auto& [name, symbol] : scope->getSymbols()) {
838 if (!symbols.contains(name)) {
839 symbols[name] = symbol;
842 getAllSymbols(scope->getParent(), symbols);
845void SemanticAnalyzer::validateClipReference(
const std::string& clip_name,
848 std::string name = clip_name;
850 if (name.starts_with(
"$")) {
851 name = name.substr(1);
855 "Intermediate buffers do not have frame properties.",
859 if (mode != Mode::VkExpr) {
861 "Intermediate buffers are only available in VkExpr mode.",
865 if (*buf_idx < 0 || *buf_idx >= num_intermediate_inputs) {
867 std::format(
"Buffer '{}' is out of range for this stage.",
875 reportError(std::format(
"Invalid clip identifier '{}'.", clip_name),
877 }
else if (*clip_idx_opt > num_inputs - 1) {
878 reportError(std::format(
"Clip index '{}' is out of range", name),
883 auto symbol = current_scope->resolve(name);
884 if (!symbol || symbol->type != Type::Clip) {
887 "Clip '{}' is not a clip constant or Clip parameter.",
894Type SemanticAnalyzer::analyze(
const PropAccessExpr& expr) {
895 validateClipReference(expr.clip.value, expr.range,
false);
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 "
908 validateClipReference(expr.clip.value, expr.range);
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.",
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.",
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());
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.",
941 std::string array_name = var_expr->name.value;
942 auto symbol = resolveSymbol(array_name, expr.range);
944 if (!symbol || symbol->type != Type::Array) {
945 reportError(std::format(
"Variable '{}' is not an array.", array_name),
949 expr.array_symbol = symbol;
951 auto index_type = analyzeExpr(expr.index.get());
953 reportError(
"Array index must be convertible to a value.",
954 expr.index->range());
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);
968void SemanticAnalyzer::analyze(AssignStmt& stmt) {
969 if (stmt.name.value ==
"RESULT") {
971 if (mode == Mode::Expr || mode == Mode::VkExpr) {
972 if (current_function ==
nullptr && scope_stack.empty()) {
973 result_defined_in_global_scope =
true;
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) {
983 std::format(
"{}() requires exactly 1 argument (size).",
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);
994 if (call_expr->callee ==
"resize") {
995 if (mode == Mode::Expr || mode == Mode::VkExpr) {
997 "resize() is only available in SingleExpr mode.",
1000 if (!is_already_array) {
1003 "Variable '{}' is undefined or not an array. ",
1008 if (is_already_array) {
1010 std::format(
"Cannot reallocate array '{}'.", var_name),
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.",
1022 auto size_type = analyzeExpr(call_expr->args[0].get());
1024 reportError(
"Array size must be convertible to a value.",
1029 auto symbol = defineSymbol(SymbolKind::Variable, var_name,
1030 Type::Array, stmt.range);
1031 stmt.symbol = symbol;
1036 auto value_type = analyzeExpr(stmt.value.get());
1038 reportError(std::format(
"Variable assignment value must be convertible "
1039 "to a value, got {}.",
1044 std::string var_name = stmt.name.value;
1045 auto existing_symbol = current_scope->resolve(var_name);
1047 if (existing_symbol && existing_symbol->type == Type::Array) {
1050 "Variable '{}' is an array and cannot be reassigned to a "
1057 defineSymbol(SymbolKind::Variable, var_name, Type::Value, stmt.range);
1058 stmt.symbol = symbol;
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.",
1069 analyzeExpr(stmt.target.get());
1071 auto value_type = analyzeExpr(stmt.value.get());
1073 reportError(
"Array assignment value must be convertible to a value.",
1074 stmt.value->range());
1078void SemanticAnalyzer::analyze(
const BlockStmt& stmt) {
1079 ScopeGuard scope_guard(
this);
1081 for (
const auto& s : stmt.statements) {
1082 analyzeStmt(s.get());
1086void SemanticAnalyzer::analyze(IfStmt& stmt) {
1087 auto cond_type = analyzeExpr(stmt.condition.get());
1089 reportError(std::format(
"If condition has type '{}' which is not "
1090 "convertible to a value.",
1092 stmt.condition->range());
1095 analyzeStmt(stmt.then_branch.get());
1097 if (stmt.else_branch) {
1098 analyzeStmt(stmt.else_branch.get());
1102void SemanticAnalyzer::analyze(WhileStmt& stmt) {
1103 auto cond_type = analyzeExpr(stmt.condition.get());
1105 reportError(std::format(
"While condition has type '{}' which is not "
1106 "convertible to a value.",
1108 stmt.condition->range());
1111 analyzeStmt(stmt.body.get());
1114void SemanticAnalyzer::analyze(
const ReturnStmt& stmt) {
1115 if (current_function ==
nullptr) {
1116 reportError(
"'return' statements are not allowed in the global scope.",
1120 auto result_type = analyzeExpr(stmt.value.get());
1121 if (result_type == Type::Array) {
1122 reportError(
"Functions cannot return arrays.", stmt.range);
1127void SemanticAnalyzer::analyze(LabelStmt& stmt) {
1128 auto symbol = defineSymbol(SymbolKind::Label, stmt.name.value, Type::Value,
1130 stmt.symbol = symbol;
1132 current_labels_seen.insert(stmt.name.value);
1134 if (current_pending_gotos.contains(stmt.name.value)) {
1135 symbol->is_used =
true;
1137 std::map<std::string, std::shared_ptr<Symbol>> symbols_at_label;
1138 getAllSymbols(current_scope, symbols_at_label);
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) {
1144 if (symbol_at_label->kind == SymbolKind::Variable &&
1145 !goto_info.symbols_at_goto.contains(name)) {
1148 "goto jumps over initialization of variable '{}'",
1150 goto_info.stmt->range);
1154 current_pending_gotos.erase(stmt.name.value);
1158void SemanticAnalyzer::analyze(GotoStmt& stmt) {
1159 if (current_function !=
nullptr) {
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),
1167 if (!current_function_labels.contains(stmt.label.value)) {
1169 std::format(
"goto target '{}' not found in function '{}'",
1170 stmt.label.value, current_function->name),
1175 if (!global_labels.contains(stmt.label.value)) {
1177 std::format(
"goto target '{}' not found in global scope",
1183 if (!current_labels_seen.contains(stmt.label.value)) {
1185 ForwardGotoInfo info;
1187 getAllSymbols(current_scope, info.symbols_at_goto);
1188 current_pending_gotos[stmt.label.value].push_back(info);
1191 auto symbol = current_scope->resolve(stmt.label.value);
1193 symbol->is_used =
true;
1195 stmt.target_label_symbol = symbol;
1197 if (stmt.condition) {
1198 analyzeExpr(stmt.condition.get());
1202void SemanticAnalyzer::analyze(FunctionDef& stmt) {
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();
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,
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()) {
1223 for (
size_t i = 0; i < s.params.size(); ++i) {
1224 if (s.params[i].type != stmt.params[i].type) {
1237 if (sig ==
nullptr) {
1238 reportError(std::format(
"Could not find signature for function '{}'",
1241 current_function_labels = saved_current_function_labels;
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());
1251 if (!body_always_returns) {
1254 "Not all control paths in function '{}' return a value.",
1261 const auto* saved_current_function = current_function;
1262 current_function = sig;
1265 function_call_stack.push_back(stmt.name.value);
1268 ScopeGuard scope_guard(
this);
1271 for (
const auto& param : stmt.params) {
1272 defineSymbol(SymbolKind::Parameter, param.name.value, param.type,
1277 analyze(*stmt.body);
1280 for (
const auto& [name, symbol] : current_scope->getSymbols()) {
1281 if (!symbol->is_used) {
1282 if (symbol->name ==
"_") {
1286 if (symbol->definition_range.start.line <= library_line_count) {
1289 reportWarning(std::format(
"Unused symbol '{}'", name),
1290 symbol->definition_range);
1296 function_call_stack.pop_back();
1299 current_function = saved_current_function;
1300 current_function_labels = saved_current_function_labels;
1303 current_pending_gotos = std::move(saved_pending_gotos);
1304 current_labels_seen = std::move(saved_labels_seen);
1307void SemanticAnalyzer::analyze([[maybe_unused]]
const GlobalDecl& stmt) {
1311void SemanticAnalyzer::collectLabels(Stmt* stmt, std::set<std::string>& labels,
1312 const std::string& context,
1313 const Range& context_range) {
1314 if (stmt ==
nullptr) {
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),
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);
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,
1334 }
else if (
auto* while_s = get_if<WhileStmt>(stmt)) {
1335 collectLabels(while_s->body.get(), labels, context, context_range);
1339bool SemanticAnalyzer::pathAlwaysReturns(Stmt* stmt) {
1340 if (stmt ==
nullptr) {
1344 if (get_if<ReturnStmt>(stmt) !=
nullptr) {
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()));
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());
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);
1368void SemanticAnalyzer::validateGlobalDependencies(Stmt* stmt) {
1369 if (stmt ==
nullptr) {
1373 std::function<void(
Expr*)> check_expr = [&](
Expr* expr) {
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());
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());
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());
1411void SemanticAnalyzer::validateFunctionCall(
const CallExpr& expr) {
1412 if (!function_signatures.contains(expr.callee)) {
1416 const FunctionSignature* sig = expr.resolved_signature;
1417 if (sig ==
nullptr) {
1419 std::ranges::find_if(function_signatures.at(expr.callee),
1420 [&](
const FunctionSignature& s) {
1421 return s.params.size() == expr.args.size();
1423 if (it != function_signatures.at(expr.callee).end()) {
1428 if (sig ==
nullptr) {
1432 if (sig->global_mode == GlobalMode::All) {
1433 for (
const auto& global_name : sig->used_globals) {
1434 if (global_name.starts_with(
"$")) {
1436 std::format(
"Invalid global variable name {}", global_name),
1440 if (!defined_global_vars.contains(global_name)) {
1443 "Function '{}' uses global variable '{}' which is "
1444 "not defined in global scope before this call",
1445 expr.callee, global_name),
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)) {
1454 "Function '{}' requires global variable '{}' which is "
1455 "not defined in global scope before this call",
1456 expr.callee, global_name),
1463void SemanticAnalyzer::collectUsedGlobals(
Expr* expr,
1464 std::set<std::string>& used_globals) {
1465 if (expr ==
nullptr) {
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);
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);
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);
1495void SemanticAnalyzer::collectUsedGlobalsInStmt(
1496 Stmt* stmt, std::set<std::string>& used_globals) {
1497 if (stmt ==
nullptr) {
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);
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);
1521 }
else if (
auto* goto_stmt = get_if<GotoStmt>(stmt)) {
1522 if (goto_stmt->condition) {
1523 collectUsedGlobals(goto_stmt->condition.get(), used_globals);
1525 }
else if (
auto* block = get_if<BlockStmt>(stmt)) {
1526 for (
const auto& s : block->statements) {
1527 collectUsedGlobalsInStmt(s.get(), used_globals);
consteval std::string_view enum_name()
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)
auto get_if(Wrapper *wrapper) -> decltype(std::get_if< T >(&wrapper->value))
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()
const OverloadCandidate< T > * select_best_candidate(std::vector< OverloadCandidate< T > > &candidates)
std::string token_type_to_string(TokenType type)
std::optional< int > get_clip_index(const std::string &s)
std::set< std::string > specific_globals
std::set< std::string > used_globals
std::vector< ParameterInfo > params
std::vector< std::unique_ptr< Stmt > > statements