diff --git a/MISSION-FAZ3.md b/MISSION-FAZ3.md new file mode 100644 index 0000000..40bb6cd --- /dev/null +++ b/MISSION-FAZ3.md @@ -0,0 +1,326 @@ +# MISSION — Faz 3: Semantik Analiz (Gitea #72) + +> **Bu dosya kendi kendine yeter.** "MISSION-FAZ3'ü oku ve yap" denince +> doğrudan uygula. Bitince DOĞRULAMA bölümünü çalıştır, #72'ye tamamlanma +> yorumu düş, bu dosyayı sil veya MISSION-FAZ4.md ile değiştir. +> `Co-Authored-By: Claude Opus 4.8` trailer'ı unutma. + +--- + +## 0) HEDEF & BAŞARI KRİTERLERİ (#72) + +İki modül: **TypeChecker** (her ExpressionNode'a tip ata) + +**StructuralValidator** (kontrol akışı kuralları). + +Bitti tanımı: +1. `build/saqut check file:examples/fibonacci.sqt` → 0 hata 0 uyarı; her + expression node'unun `resolvedType` alanı JSON'da dolu. +2. `examples/semantic/` test fixture'ları doğru hata/uyarı üretiyor. +3. `-Wall -Wextra` temiz. Faz 0/1/2 testleri hâlâ geçer. + +--- + +## 1) DÖNÜŞÜM KURALLARI (ADR-010 özeti) + +| Durum | Sonuç | Kod | +|---|---|---| +| `float x = 1` | Geçerli — literal 1.0 sayılır | — | +| `double x = 1` | Geçerli | — | +| `float x = intVar` | **Uyarı** — örtük genişletme, veri kaybı yok | W004 | +| `double x = intVar` | **Uyarı** | W004 | +| `double x = floatVar` | **Uyarı** | W004 | +| `int x = 1.5` | **Hata** — literal'den veri kaybı | E003 | +| `int x = floatVar` | **Hata** — daraltma | E003 | +| `float x = doubleVar` | **Hata** — daraltma | E003 | +| `int x = intVar` | Geçerli | — | + +**Kural özeti:** +- Literal bağlama-göre tiplenir: hedef tip daha geniş veya aynıysa uyarısız geçer. +- Değişken→değişken: genişletme (widening) = W004, daraltma (narrowing) = E003. +- Aynı tip = her zaman geçerli. + +**Yardımcı fonksiyon (type_checker içinde):** +```cpp +enum class ConvResult { Ok, Warn_W004, Error_E003 }; +ConvResult checkAssignCompat(const Type& target, const Type& src, bool srcIsLiteral); +``` + +--- + +## 2) KATALOG GÜNCELLEMESİ + +`src/diagnostic/diagnostic.hpp` → W004 ekle: +```cpp +{"W004", DiagLevel::Warning, "Örtük sayısal genişletme (widening)"}, +``` + +--- + +## 3) DOSYA DOSYA PLAN + +Yeni dizin: `src/semantic/`. CMake GLOB_RECURSE → yeni `.cpp`'ler otomatik +alınır ama `build.sh` ile derle (cmake yeniden config eder). + +--- + +### 3.1 `src/semantic/type_checker.hpp` + +```cpp +#ifndef SAQUT_SEMANTIC_TYPE_CHECKER +#define SAQUT_SEMANTIC_TYPE_CHECKER + +#include "symbol/symbol_table.hpp" +#include "diagnostic/diagnostic_engine.hpp" +#include "parser/ast_node.hpp" +#include "core/type.hpp" + +class TypeChecker { +public: + TypeChecker(SymbolTable& table, DiagnosticEngine& diag) + : table_(table), diag_(diag) {} + + void check(ASTNode* program); + +private: + // İfadeyi gez, resolvedType ata, tipi döndür. + // expectedType: bağlam tipi (literal genişletme için); boş = bilinmiyor. + Type checkExpr(ASTNode* node, const Type& expected = Type::error()); + + // Deyimi gez (statement'lar tip döndürmez). + void checkStmt(ASTNode* node); + + // Fonksiyon gövdesini gez; dönüş tipi bağlamını stack'te tut. + void checkFunction(ASTNode* fnNode); + + // Atama uyumunu denetle; uyarı/hata raporla. true = geçerli. + bool checkAssign(const Type& target, const Type& src, + bool srcIsLiteral, const SourceLocation& loc, + const std::string& context); + + SymbolTable& table_; + DiagnosticEngine& diag_; + + // Aktif fonksiyonun beklenen dönüş tipi (iç içe fonksiyon yok, stack + // yerine tek değer yeterli). + Type currentReturnType_; + bool inFunction_ = false; +}; + +#endif // SAQUT_SEMANTIC_TYPE_CHECKER +``` + +--- + +### 3.2 `src/semantic/type_checker.cpp` + +`check(program)`: +- Program children'ı gez: FunctionDecl → `checkFunction`, VariableDecl → + `checkStmt`, StructDecl → atla. + +`checkFunction(fnNode)`: +- `inFunction_ = true; currentReturnType_ = typeFromReturnType(fn->returnType);` +- `checkStmt(body)` (body = children[0]) +- `inFunction_ = false;` + +`checkStmt(node)` — switch(node->kind): +- **Block**: her child → `checkStmt`. +- **VariableDecl**: initExpr varsa `checkExpr(initExpr, typeFromName(vd->varType))`; + sonucu target tiple `checkAssign` ile denetle. +- **ExpressionStatement**: `checkExpr(es->expression)`. +- **ReturnStatement**: value varsa `checkExpr(value, currentReturnType_)`; + sonucu `checkAssign(currentReturnType_, valueType, isLiteral, loc, "return")`. + Değer yok ve returnType != void → E006. +- **IfStatement**: condition checkExpr (bool beklenir, ama şimdilik herhangi + sayısal tip de geçerli — E003 değil, TODO Faz3+); then/else checkStmt. +- **WhileStatement / DoWhileStatement**: condition checkExpr; body checkStmt. +- **ForStatement**: init checkStmt; condition checkExpr; update checkExpr; body checkStmt. +- **Break / Continue / ExpressionStatement**: içerik checkExpr. +- **ReturnStatement**: yukarıda. + +`checkExpr(node, expected)` — switch(node->kind) → Type döndürür: +- **Literal(INTEGER)**: + - expected sayısal ve daha geniş veya aynıysa → `expected` tipini döndür + (literal 1.0 olarak tiplenir, uyarısız). + - expected int veya error → `Type::Int()`. + - expected float literal'e uymuyor (ör. expected=int, literal=FLOAT) → E003. +- **Literal(FLOAT)**: expected double ise double döndür (kayıpsız); expected int → E003; + aksi → `Type::Float()`. +- **Literal(BOOLEAN)**: `Type::Bool()`. +- **Literal(STRING)**: `Type::String()`. +- **Literal(BOŞ)**: `Type::error()` (null — Faz 3+ kapsam dışı). +- **Identifier**: `id->resolvedSymbol ? id->resolvedSymbol->type : Type::error()`. + Type::error() ise E001 zaten Faz 2'de raporlandı, sessiz geç. +- **BinaryExpression**: + - Atama operatörü (EQUAL, +=, -=, vb.): + - left = `checkExpr(left)`, right = `checkExpr(right, leftType)`. + - `checkAssign(leftType, rightType, isLiteralRight, loc, "atama")`. + - `resolvedType = leftType`. + - Aritmetik (+, -, *, /, %): + - Her iki operandı `checkExpr` et. + - İkisi de sayısal ve aynı tip → sonuç o tip. + - İkisi sayısal farklı tip → `checkAssign` mantığıyla uyar/hata; sonuç + daha geniş tip (veya E003 durumunda error). + - Sayısal değil → E003, `Type::error()`. + - Karşılaştırma (==, !=, <, <=, >, >=): + - Operandları checkExpr; uyumlu sayısal veya aynı tip → `Type::Bool()`. + - Uyumsuz → E003, `Type::Bool()` (hata yayılmasın, bool olarak devam). + - Mantıksal (&&, ||): + - Her iki operand bool beklenir; değilse E003. + - Sonuç `Type::Bool()`. + - Unary prefix (-, +, !): + - Left = nullptr; Right = operand. + - `-`/`+`: sayısal beklenir; `!`: bool beklenir. + - resolvedType'a sonucu ata. +- **Call**: + - callee = `checkExpr(callee)` → Function tipi beklenir. + - Argüman sayısı imzayla eşleşmeli → E008. + - Her argüman: `checkExpr(arg, paramType[i])` → `checkAssign(paramType, argType, ...)`. + - resolvedType = callee tipi's `returnType`. +- **Postfix (++/--)**: operand sayısal → sonuç aynı tip; değilse E003. +- **MemberAccess**: `Type::error()` + TODO (struct alan çözümü Faz 3+). +- **IndexExpression**: TODO; `Type::error()`. + +`checkAssign(target, src, srcIsLiteral, loc, ctx)`: +- target veya src = error → `return true` (sessiz geç, hata zaten raporlandı). +- target == src → `return true`. +- Genişletme (widening): + - `int → float`: srcIsLiteral → geçerli (**uyarısız**); değil → **W004**. + - `int → double`, `float → double`: aynı kural. + - srcIsLiteral ve kayıpsız → `return true`. + - srcIsLiteral değil → W004 raporla, `return true` (yine de geçerli). +- Daraltma (narrowing): + - `float → int`, `double → int`, `double → float` → **E003**, `return false`. +- Tamamen farklı tipler (int ↔ string, vb.) → **E003**, `return false`. + +> `srcIsLiteral`: `node->kind == ASTKind::Literal` ise true. + +--- + +### 3.3 `src/semantic/structural_validator.hpp` + +```cpp +#ifndef SAQUT_SEMANTIC_STRUCTURAL_VALIDATOR +#define SAQUT_SEMANTIC_STRUCTURAL_VALIDATOR + +#include "diagnostic/diagnostic_engine.hpp" +#include "parser/ast_node.hpp" + +class StructuralValidator { +public: + StructuralValidator(DiagnosticEngine& diag) : diag_(diag) {} + void validate(ASTNode* program); + +private: + void walkDecl(ASTNode* node); + void walkStmt(ASTNode* node); + + DiagnosticEngine& diag_; + int loopDepth_ = 0; + bool inFunction_ = false; +}; + +#endif // SAQUT_SEMANTIC_STRUCTURAL_VALIDATOR +``` + +--- + +### 3.4 `src/semantic/structural_validator.cpp` + +`validate(program)`: her top-level child → `walkDecl`. + +`walkDecl`: +- FunctionDecl: `inFunction_=true`, `walkStmt(body)`, `inFunction_=false`. +- VariableDecl / StructDecl: atla. + +`walkStmt(node)` — switch: +- **Block**: her child `walkStmt`. +- **IfStatement**: then/else `walkStmt`. +- **WhileStatement / DoWhileStatement**: `loopDepth_++; walkStmt(body); loopDepth_--;` +- **ForStatement**: `loopDepth_++; walkStmt(init?); walkStmt(body); loopDepth_--;` +- **BreakStatement**: `loopDepth_ == 0` → E004. +- **ContinueStatement**: `loopDepth_ == 0` → E004. +- **ReturnStatement**: `!inFunction_` → E005. + (Return tip uyumu TypeChecker'ın işi; burada sadece yapısal kural.) +- **ExpressionStatement / VariableDecl**: `walkStmt` çocukları için yinele. +- Diğer: atla. + +--- + +## 4) CLI — `saqut check` KOMUTU + +### 4.1 `src/cli/commands/check.hpp` + +```cpp +inline int cmdCheck(const CliArgs& args) { + // tokenize → parse → symbolCollect → typeCheck → structValidate + // Tüm hatalar DiagnosticEngine'de toplanır. + // Çıktı: JSON (--compact destekli) + // {"file":"...","diagnostics":{...}} + // exit code: hasErrors() ? 1 : 0 +} +``` + +### 4.2 `src/cli/cli.hpp` veya `main.cpp` + +`check` komutu kaydet; `parseArgs` tanımlı komutlar listesine ekle. + +--- + +## 5) TEST FIXTURE'LARI — `examples/semantic/` + +| Dosya | İçerik | Beklenen | +|---|---|---| +| `widening.sqt` | `float x = 1;` (OK) + `float y = someInt;` | W004 | +| `narrowing.sqt` | `int x = 1.5;` | E003 | +| `bad_return.sqt` | `int foo() { return 1.5; }` | E003 | +| `break_outside.sqt` | top-level `break;` | E004 | +| `return_outside.sqt` | top-level `return 0;` | E005 | +| `bad_args.sqt` | `fibonacci(1, 2)` (1 parametre bekliyor) | E008 | + +--- + +## 6) DOĞRULAMA KOMUTLARI + +```bash +bash scripts/build.sh + +# Başarı: 0 hata 0 uyarı +build/saqut check file:examples/fibonacci.sqt + +# Her expression tipli mi? +build/saqut ast file:examples/fibonacci.sqt | python3 -c " +import json,sys +def walk(n): + if 'resolvedType' in n and n['resolvedType'] is None: + print('UNTIPPED:', n.get('kind'), n.get('name','')) + for v in n.values(): + if isinstance(v, dict): walk(v) + elif isinstance(v, list): [walk(i) for i in v if isinstance(i, dict)] +walk(json.load(sys.stdin))" + +# Hata fixture'ları +build/saqut check file:examples/semantic/widening.sqt # W004 +build/saqut check file:examples/semantic/narrowing.sqt # E003 +build/saqut check file:examples/semantic/break_outside.sqt # E004 +build/saqut check file:examples/semantic/bad_args.sqt # E008 + +# Regresyon +bash tests/run.sh +build/saqut ast file:examples/fibonacci.sqt | python3 -m json.tool >/dev/null && echo AST-OK +``` + +--- + +## 7) HAFIZA İPUÇLARI + +- `ASTKind::BinaryExpression` → `BinaryExpressionNode{Left, Right, Operator(TokenType)}` +- Atama operatörleri: `EQUAL, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, SLASH_EQUAL, ...` +- `ASTKind::Call` → `CallExpressionNode{callee, arguments}` +- `ASTKind::Literal` → `LiteralNode{literalType(INTEGER/FLOAT/...), parserToken}` +- `ASTKind::Identifier` → `IdentifierNode{resolvedSymbol}` (Faz 2'de set edildi) +- `FunctionDeclNode{name, returnType(string), params, children=[body]}` +- `Type::fromName(str)` primitifi çözer; `Type::function(ret, params)` fonksiyon tipi +- `diag_.report("E003", loc, "mesaj")` → katalogdan seviye otomatik çözülür +- `resolvedType` → `ExpressionNode`'un alanı (ast_node.hpp) +- Literal'ın literal tipi: `((LiteralNode*)node)->literalType` +- İki sayısal tipin genişliği: int < float < double (basit sıralama yeterli) diff --git a/build/.ninja_deps b/build/.ninja_deps index b8c5440..d94e647 100644 Binary files a/build/.ninja_deps and b/build/.ninja_deps differ diff --git a/build/.ninja_log b/build/.ninja_log index fef9a49..3ec7152 100644 --- a/build/.ninja_log +++ b/build/.ninja_log @@ -1,37 +1,23 @@ # ninja log v7 -0 5993 1781788863286900733 CMakeFiles/saqut.dir/src/core/sourcefile.cpp.o da6f5fc90e87e6b1 -1 5798 1781788863287900734 CMakeFiles/saqut.dir/src/lexer/lexer.cpp.o 90eeec811f2137e6 -8108 10913 1781788871394910212 CMakeFiles/saqut.dir/src/tokenizer/tokenizer.cpp.o a01677f8bb4f4dbc -5798 10663 1781788869086425023 CMakeFiles/saqut.dir/src/parser/parser.cpp.o 2c65b7be26cead32 -5 8301 1781788863292536727 CMakeFiles/saqut.dir/src/parser/nodes/program.cpp.o ac5bbcd74d87561a -3 8830 1781788863290580789 CMakeFiles/saqut.dir/src/parser/nodes/identifier.cpp.o eb96bb4b1eb4ad80 -2 8108 1781788863290412878 CMakeFiles/saqut.dir/src/parser/nodes/expressions.cpp.o 4057e3d63c63a1ab -3 8768 1781788863290742048 CMakeFiles/saqut.dir/src/parser/nodes/literal.cpp.o 78f2c4da7c9b2281 -8 8333 1781788863295900743 CMakeFiles/saqut.dir/src/parser/nodes/statements.cpp.o b5c20724bbf3648c -1 9121 1781788863288931744 CMakeFiles/saqut.dir/src/parser/nodes/binary_expr.cpp.o d2e2bb2f8a63c6d2 -1 8732 1781788863289045597 CMakeFiles/saqut.dir/src/parser/nodes/declarations.cpp.o b6c56f04a257f685 -1 10559 1781788863288812886 CMakeFiles/saqut.dir/src/main.cpp.o 110c26cb1d0c3a23 -11577 11832 1781788874863914253 saqut 8525928b86934b0a -0 22 1781788881496630820 build.ninja 1876a59d627a585 -0 22 1781788881495921955 /home/saqut/Masaüstü/saqutcompiler/build/cmake_install.cmake 1876a59d627a585 -5993 11577 1781788869279907744 CMakeFiles/saqut.dir/src/symbol/symbol_collector.cpp.o 3348f498f369213d -1 3694 1781788886610186419 CMakeFiles/saqut.dir/src/lexer/lexer.cpp.o 90eeec811f2137e6 1 6106 1781788886608927872 CMakeFiles/saqut.dir/src/core/sourcefile.cpp.o da6f5fc90e87e6b1 -3 8929 1781788886610927874 CMakeFiles/saqut.dir/src/parser/nodes/identifier.cpp.o eb96bb4b1eb4ad80 -3 9947 1781788886611876031 CMakeFiles/saqut.dir/src/parser/nodes/program.cpp.o ac5bbcd74d87561a -3 10066 1781788886611770636 CMakeFiles/saqut.dir/src/parser/nodes/literal.cpp.o 78f2c4da7c9b2281 -1 10950 1781788886610366872 CMakeFiles/saqut.dir/src/parser/nodes/binary_expr.cpp.o d2e2bb2f8a63c6d2 -2 11212 1781788886610587227 CMakeFiles/saqut.dir/src/parser/nodes/expressions.cpp.o 4057e3d63c63a1ab -1 11244 1781788886610473997 CMakeFiles/saqut.dir/src/parser/nodes/declarations.cpp.o b6c56f04a257f685 -3694 11807 1781788890301932134 CMakeFiles/saqut.dir/src/parser/parser.cpp.o 2c65b7be26cead32 -7 12109 1781788886614927879 CMakeFiles/saqut.dir/src/parser/nodes/statements.cpp.o b5c20724bbf3648c -1 12570 1781788886610272711 CMakeFiles/saqut.dir/src/main.cpp.o 110c26cb1d0c3a23 +1 3694 1781788886610186419 CMakeFiles/saqut.dir/src/lexer/lexer.cpp.o 90eeec811f2137e6 8929 12696 1781788895536938159 CMakeFiles/saqut.dir/src/tokenizer/tokenizer.cpp.o a01677f8bb4f4dbc -6106 13677 1781788892713934912 CMakeFiles/saqut.dir/src/symbol/symbol_collector.cpp.o 3348f498f369213d -13677 13929 1781788900284943608 saqut 8525928b86934b0a -1 4141 1781789523129566061 CMakeFiles/saqut.dir/src/main.cpp.o 110c26cb1d0c3a23 -4141 4368 1781789527269569791 saqut 8525928b86934b0a -1 4518 1781789532082574122 CMakeFiles/saqut.dir/src/main.cpp.o 110c26cb1d0c3a23 -4518 4748 1781789536593578177 saqut 8525928b86934b0a 1 2516 1781789879291876730 CMakeFiles/saqut.dir/src/parser/parser.cpp.o 2c65b7be26cead32 -2516 2743 1781789881806878867 saqut 8525928b86934b0a +3 9947 1781788886611876031 CMakeFiles/saqut.dir/src/parser/nodes/program.cpp.o ac5bbcd74d87561a +3 8929 1781788886610927874 CMakeFiles/saqut.dir/src/parser/nodes/identifier.cpp.o eb96bb4b1eb4ad80 +2 11212 1781788886610587227 CMakeFiles/saqut.dir/src/parser/nodes/expressions.cpp.o 4057e3d63c63a1ab +3 10066 1781788886611770636 CMakeFiles/saqut.dir/src/parser/nodes/literal.cpp.o 78f2c4da7c9b2281 +7 12109 1781788886614927879 CMakeFiles/saqut.dir/src/parser/nodes/statements.cpp.o b5c20724bbf3648c +1 10950 1781788886610366872 CMakeFiles/saqut.dir/src/parser/nodes/binary_expr.cpp.o d2e2bb2f8a63c6d2 +1 11244 1781788886610473997 CMakeFiles/saqut.dir/src/parser/nodes/declarations.cpp.o b6c56f04a257f685 +1 5243 1781792343880679446 CMakeFiles/saqut.dir/src/main.cpp.o 110c26cb1d0c3a23 +5243 5478 1781792349122684689 saqut 77cf84e33c34ab02 +0 22 1781792352391905736 build.ninja 1876a59d627a585 +0 22 1781792352391378326 /home/saqut/Masaüstü/saqutcompiler/build/cmake_install.cmake 1876a59d627a585 +1 3851 1781792343882099316 CMakeFiles/saqut.dir/src/symbol/symbol_collector.cpp.o 3348f498f369213d +1 3077 1781792343881863451 CMakeFiles/saqut.dir/src/semantic/structural_validator.cpp.o 4bfec8abc0e9893e +1 3624 1781792343881974165 CMakeFiles/saqut.dir/src/semantic/type_checker.cpp.o 15f44776b9c3e26d +1 3032 1781792355630982632 CMakeFiles/saqut.dir/src/semantic/structural_validator.cpp.o 4bfec8abc0e9893e +1 3534 1781792355631085471 CMakeFiles/saqut.dir/src/semantic/type_checker.cpp.o 15f44776b9c3e26d +1 5538 1781792355629691194 CMakeFiles/saqut.dir/src/main.cpp.o 110c26cb1d0c3a23 +5538 5861 1781792361166696724 saqut 77cf84e33c34ab02 diff --git a/build/build.ninja b/build/build.ninja index 3bf1809..d4cf748 100644 --- a/build/build.ninja +++ b/build/build.ninja @@ -148,6 +148,24 @@ build CMakeFiles/saqut.dir/src/parser/parser.cpp.o: CXX_COMPILER__saqut_unscanne OBJECT_FILE_DIR = CMakeFiles/saqut.dir/src/parser TARGET_SUPPORT_DIR = CMakeFiles/saqut.dir +build CMakeFiles/saqut.dir/src/semantic/structural_validator.cpp.o: CXX_COMPILER__saqut_unscanned_Debug /home/saqut/Masaüstü/saqutcompiler/src/semantic/structural_validator.cpp || cmake_object_order_depends_target_saqut + CONFIG = Debug + DEP_FILE = CMakeFiles/saqut.dir/src/semantic/structural_validator.cpp.o.d + FLAGS = -g -std=gnu++20 -Wall -Wextra -g -O0 + INCLUDES = -I/home/saqut/Masaüstü/saqutcompiler/src + OBJECT_DIR = CMakeFiles/saqut.dir + OBJECT_FILE_DIR = CMakeFiles/saqut.dir/src/semantic + TARGET_SUPPORT_DIR = CMakeFiles/saqut.dir + +build CMakeFiles/saqut.dir/src/semantic/type_checker.cpp.o: CXX_COMPILER__saqut_unscanned_Debug /home/saqut/Masaüstü/saqutcompiler/src/semantic/type_checker.cpp || cmake_object_order_depends_target_saqut + CONFIG = Debug + DEP_FILE = CMakeFiles/saqut.dir/src/semantic/type_checker.cpp.o.d + FLAGS = -g -std=gnu++20 -Wall -Wextra -g -O0 + INCLUDES = -I/home/saqut/Masaüstü/saqutcompiler/src + OBJECT_DIR = CMakeFiles/saqut.dir + OBJECT_FILE_DIR = CMakeFiles/saqut.dir/src/semantic + TARGET_SUPPORT_DIR = CMakeFiles/saqut.dir + build CMakeFiles/saqut.dir/src/symbol/symbol_collector.cpp.o: CXX_COMPILER__saqut_unscanned_Debug /home/saqut/Masaüstü/saqutcompiler/src/symbol/symbol_collector.cpp || cmake_object_order_depends_target_saqut CONFIG = Debug DEP_FILE = CMakeFiles/saqut.dir/src/symbol/symbol_collector.cpp.o.d @@ -174,7 +192,7 @@ build CMakeFiles/saqut.dir/src/tokenizer/tokenizer.cpp.o: CXX_COMPILER__saqut_un ############################################# # Link the executable saqut -build saqut: CXX_EXECUTABLE_LINKER__saqut_Debug CMakeFiles/saqut.dir/src/core/sourcefile.cpp.o CMakeFiles/saqut.dir/src/lexer/lexer.cpp.o CMakeFiles/saqut.dir/src/main.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/binary_expr.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/declarations.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/expressions.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/identifier.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/literal.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/program.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/statements.cpp.o CMakeFiles/saqut.dir/src/parser/parser.cpp.o CMakeFiles/saqut.dir/src/symbol/symbol_collector.cpp.o CMakeFiles/saqut.dir/src/tokenizer/tokenizer.cpp.o +build saqut: CXX_EXECUTABLE_LINKER__saqut_Debug CMakeFiles/saqut.dir/src/core/sourcefile.cpp.o CMakeFiles/saqut.dir/src/lexer/lexer.cpp.o CMakeFiles/saqut.dir/src/main.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/binary_expr.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/declarations.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/expressions.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/identifier.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/literal.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/program.cpp.o CMakeFiles/saqut.dir/src/parser/nodes/statements.cpp.o CMakeFiles/saqut.dir/src/parser/parser.cpp.o CMakeFiles/saqut.dir/src/semantic/structural_validator.cpp.o CMakeFiles/saqut.dir/src/semantic/type_checker.cpp.o CMakeFiles/saqut.dir/src/symbol/symbol_collector.cpp.o CMakeFiles/saqut.dir/src/tokenizer/tokenizer.cpp.o CONFIG = Debug DEP_FILE = CMakeFiles/saqut.dir/link.d FLAGS = -g diff --git a/examples/semantic/bad_args.sqt b/examples/semantic/bad_args.sqt new file mode 100644 index 0000000..8b037ee --- /dev/null +++ b/examples/semantic/bad_args.sqt @@ -0,0 +1,9 @@ +// E008 — yanlış argüman sayısı +int add(int a, int b) { + return a + b; +} + +int main() { + int x = add(1, 2, 3); + return 0; +} diff --git a/examples/semantic/bad_return.sqt b/examples/semantic/bad_return.sqt new file mode 100644 index 0000000..7b1c048 --- /dev/null +++ b/examples/semantic/bad_return.sqt @@ -0,0 +1,8 @@ +// E003 — return tipi uyuşmazlığı +int foo() { + return 1.5; +} + +int main() { + return 0; +} diff --git a/examples/semantic/break_outside.sqt b/examples/semantic/break_outside.sqt new file mode 100644 index 0000000..223ee8e --- /dev/null +++ b/examples/semantic/break_outside.sqt @@ -0,0 +1,5 @@ +// E004 — break döngü dışında +int main() { + break; + return 0; +} diff --git a/examples/semantic/narrowing.sqt b/examples/semantic/narrowing.sqt new file mode 100644 index 0000000..bdb975f --- /dev/null +++ b/examples/semantic/narrowing.sqt @@ -0,0 +1,5 @@ +// E003 — daraltma: float literal → int +int main() { + int x = 1.5; + return 0; +} diff --git a/examples/semantic/widening.sqt b/examples/semantic/widening.sqt new file mode 100644 index 0000000..c605336 --- /dev/null +++ b/examples/semantic/widening.sqt @@ -0,0 +1,7 @@ +// W004 — örtük genişletme: değişken→değişken int→float +int main() { + int a = 5; + float b = 1; // OK: literal bağlama-göre tiplenir, uyarısız + float c = a; // W004: int değişken → float değişken + return 0; +} diff --git a/src/cli/args.hpp b/src/cli/args.hpp index 31317b0..123e1f1 100644 --- a/src/cli/args.hpp +++ b/src/cli/args.hpp @@ -88,8 +88,8 @@ inline CliArgs parseArgs(int argc, char* argv[]) { // İlk argüman komut mu? if (args.command.empty() && i == 1) { if (arg == "run" || arg == "tokens" || arg == "ast" || - arg == "symbols" || arg == "compile" || arg == "parse" || - arg == "transpile" || arg == "interpret") { + arg == "symbols" || arg == "check" || arg == "compile" || + arg == "parse" || arg == "transpile" || arg == "interpret") { args.command = arg; continue; } diff --git a/src/cli/commands/check.hpp b/src/cli/commands/check.hpp new file mode 100644 index 0000000..9e2943f --- /dev/null +++ b/src/cli/commands/check.hpp @@ -0,0 +1,59 @@ +#ifndef SAQUT_CLI_CHECK +#define SAQUT_CLI_CHECK + +#include +#include "cli/args.hpp" +#include "tokenizer/tokenizer.hpp" +#include "parser/parser.hpp" +#include "symbol/symbol_table.hpp" +#include "symbol/symbol_collector.hpp" +#include "semantic/type_checker.hpp" +#include "semantic/structural_validator.hpp" +#include "diagnostic/diagnostic_engine.hpp" +#include "vendor/nlohmann/json.hpp" + +inline int cmdCheck(const CliArgs& args) { + std::string filePath = inputFilePath(args); + std::string source = readSource(args); + if (source.empty()) return 1; + + Tokenizer tokenizer; + auto tokens = tokenizer.scan(source, filePath); + + Parser parser; + ASTNode* ast = parser.parse(tokens); + + DiagnosticEngine diag; + + if (!ast) { + diag.report("E000", SourceLocation{}, "AST üretilemedi"); + nlohmann::json out; + out["file"] = filePath; + out["diagnostics"] = diag.toJsonObj(); + std::cout << (args.compact ? out.dump() : out.dump(2)) << "\n"; + for (auto* t : tokens) delete t; + return 1; + } + + SymbolTable table; + SymbolCollector(table, diag).collect(ast); + + // Sembol toplama hataları varsa tip denetimine geçme + // (çözümsüz semboller tip denetiminde sahte E003 üretir) + if (!diag.hasErrors()) { + TypeChecker(table, diag).check(ast); + StructuralValidator(diag).validate(ast); + } + + nlohmann::json out; + out["file"] = filePath; + out["diagnostics"] = diag.toJsonObj(); + + std::cout << (args.compact ? out.dump() : out.dump(2)) << "\n"; + + delete ast; + for (auto* t : tokens) delete t; + return diag.hasErrors() ? 1 : 0; +} + +#endif // SAQUT_CLI_CHECK diff --git a/src/diagnostic/diagnostic.hpp b/src/diagnostic/diagnostic.hpp index 5e9a3e8..307332f 100644 --- a/src/diagnostic/diagnostic.hpp +++ b/src/diagnostic/diagnostic.hpp @@ -127,6 +127,7 @@ inline const std::vector& diagnosticCatalog() { {"W001", DiagLevel::Warning, "Kullanılmayan değişken"}, {"W002", DiagLevel::Warning, "Sıfıra bölme (sabit ifade)"}, {"W003", DiagLevel::Warning, "Erişilemez (ölü) kod"}, + {"W004", DiagLevel::Warning, "Örtük sayısal genişletme (widening)"}, }; return catalog; } diff --git a/src/main.cpp b/src/main.cpp index 2f3321c..78609e3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,6 +27,7 @@ #include "cli/commands/tokens.hpp" #include "cli/commands/ast.hpp" #include "cli/commands/symbols.hpp" +#include "cli/commands/check.hpp" int main(int argc, char* argv[]) { // Komutları kaydet @@ -48,6 +49,10 @@ int main(int argc, char* argv[]) { "Sembol tablosu (fonksiyonlar, değişkenler)", false, cmdSymbols}); + cli.registerCommand({"check", + "Semantik analiz — tip denetimi + yapısal doğrulama", + false, cmdCheck}); + // --- Gelecek komutlar (TODO) --- cli.registerCommand({"compile", "TODO: Kaynak kodu derle", diff --git a/src/semantic/structural_validator.cpp b/src/semantic/structural_validator.cpp new file mode 100644 index 0000000..64f7d52 --- /dev/null +++ b/src/semantic/structural_validator.cpp @@ -0,0 +1,95 @@ +#include "semantic/structural_validator.hpp" +#include "parser/nodes/declarations.hpp" +#include "parser/nodes/statements.hpp" + +void StructuralValidator::validate(ASTNode* program) { + if (!program) return; + for (ASTNode* child : program->getChildren()) + walkDecl(child); +} + +void StructuralValidator::walkDecl(ASTNode* node) { + if (!node) return; + if (node->kind == ASTKind::FunctionDecl) { + auto* fn = (FunctionDeclNode*)node; + inFunction_ = true; + auto& ch = fn->getChildren(); + if (!ch.empty()) walkStmt(ch[0]); + inFunction_ = false; + } + // VariableDecl / StructDecl: yapısal kural yok +} + +void StructuralValidator::walkStmt(ASTNode* node) { + if (!node) return; + + switch (node->kind) { + + case ASTKind::Block: + for (ASTNode* child : node->getChildren()) walkStmt(child); + break; + + case ASTKind::IfStatement: { + auto* ifn = (IfStatementNode*)node; + if (ifn->thenBranch) walkStmt(ifn->thenBranch); + if (ifn->elseBranch) walkStmt(ifn->elseBranch); + break; + } + + case ASTKind::WhileStatement: { + auto* ws = (WhileStatementNode*)node; + loopDepth_++; + if (ws->body) walkStmt(ws->body); + loopDepth_--; + break; + } + + case ASTKind::DoWhileStatement: { + auto* dw = (DoWhileStatementNode*)node; + loopDepth_++; + if (dw->body) walkStmt(dw->body); + loopDepth_--; + break; + } + + case ASTKind::ForStatement: { + auto* fs = (ForStatementNode*)node; + loopDepth_++; + if (fs->init) walkStmt(fs->init); + if (fs->body) walkStmt(fs->body); + loopDepth_--; + break; + } + + case ASTKind::BreakStatement: + if (loopDepth_ == 0) + diag_.report("E004", node->loc, + "'break' döngü dışında kullanılamaz"); + break; + + case ASTKind::ContinueStatement: + if (loopDepth_ == 0) + diag_.report("E004", node->loc, + "'continue' döngü dışında kullanılamaz"); + break; + + case ASTKind::ReturnStatement: + if (!inFunction_) + diag_.report("E005", node->loc, + "'return' fonksiyon dışında kullanılamaz"); + break; + + case ASTKind::VariableDecl: { + // sibling'leri de gez + for (ASTNode* sib : node->getChildren()) + if (sib->kind == ASTKind::VariableDecl) walkStmt(sib); + break; + } + + case ASTKind::ExpressionStatement: + break; // ifade içinde return/break olamaz + + default: + break; + } +} diff --git a/src/semantic/structural_validator.hpp b/src/semantic/structural_validator.hpp new file mode 100644 index 0000000..aa089b8 --- /dev/null +++ b/src/semantic/structural_validator.hpp @@ -0,0 +1,22 @@ +#ifndef SAQUT_SEMANTIC_STRUCTURAL_VALIDATOR +#define SAQUT_SEMANTIC_STRUCTURAL_VALIDATOR + +#include "diagnostic/diagnostic_engine.hpp" +#include "parser/ast_node.hpp" + +class StructuralValidator { +public: + explicit StructuralValidator(DiagnosticEngine& diag) : diag_(diag) {} + + void validate(ASTNode* program); + +private: + void walkDecl(ASTNode* node); + void walkStmt(ASTNode* node); + + DiagnosticEngine& diag_; + int loopDepth_ = 0; + bool inFunction_ = false; +}; + +#endif // SAQUT_SEMANTIC_STRUCTURAL_VALIDATOR diff --git a/src/semantic/type_checker.cpp b/src/semantic/type_checker.cpp new file mode 100644 index 0000000..f037b53 --- /dev/null +++ b/src/semantic/type_checker.cpp @@ -0,0 +1,384 @@ +#include "semantic/type_checker.hpp" +#include "parser/nodes/program.hpp" +#include "parser/nodes/declarations.hpp" +#include "parser/nodes/statements.hpp" +#include "parser/nodes/expressions.hpp" +#include "parser/nodes/binary_expr.hpp" +#include "parser/nodes/identifier.hpp" +#include "parser/nodes/literal.hpp" + +// ───────────────────────────────────────────────────────────────────────────── +// Yardımcılar +// ───────────────────────────────────────────────────────────────────────────── + +int TypeChecker::numericRank(const Type& t) { + if (!t.isPrimitive()) return -1; + switch (t.prim) { + case PrimitiveKind::Int: return 0; + case PrimitiveKind::Float: return 1; + case PrimitiveKind::Double: return 2; + default: return -1; + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// check — giriş noktası +// ───────────────────────────────────────────────────────────────────────────── + +void TypeChecker::check(ASTNode* program) { + if (!program) return; + for (ASTNode* child : program->getChildren()) { + switch (child->kind) { + case ASTKind::FunctionDecl: checkFunction(child); break; + case ASTKind::VariableDecl: checkStmt(child); break; + default: break; + } + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// checkFunction +// ───────────────────────────────────────────────────────────────────────────── + +void TypeChecker::checkFunction(ASTNode* fnNode) { + auto* fn = (FunctionDeclNode*)fnNode; + inFunction_ = true; + currentReturnType_ = Type::fromName(fn->returnType); + if (currentReturnType_.isError() && fn->returnType != "void") + currentReturnType_ = Type::Void(); // bilinmeyen dönüş tipi → void gibi davran + + auto& ch = fn->getChildren(); + if (!ch.empty()) checkStmt(ch[0]); // body Block + + inFunction_ = false; +} + +// ───────────────────────────────────────────────────────────────────────────── +// checkAssign — atama uyumu + uyarı/hata raporlama +// ───────────────────────────────────────────────────────────────────────────── + +bool TypeChecker::checkAssign(const Type& target, const Type& src, + bool srcIsLiteral, + const SourceLocation& loc, + const std::string& ctx) { + if (target.isError() || src.isError()) return true; // önceki hata, sessiz geç + if (target.equals(src)) return true; + + int tRank = numericRank(target); + int sRank = numericRank(src); + + if (tRank >= 0 && sRank >= 0) { + if (tRank > sRank) { + // Genişletme (widening): int→float, int→double, float→double + if (srcIsLiteral) return true; // literal bağlama-göre tiplenir, uyarısız + diag_.report("W004", loc, + "'" + ctx + "': " + src.toString() + + " → " + target.toString() + " örtük genişletme"); + return true; + } else { + // Daraltma (narrowing): float→int, double→float, vb. + diag_.report("E003", loc, + "'" + ctx + "': " + src.toString() + + " → " + target.toString() + " daraltma (veri kaybı)"); + return false; + } + } + + // Tamamen farklı tipler + diag_.report("E003", loc, + "'" + ctx + "': " + src.toString() + + " tipi " + target.toString() + " tipine atanamaz"); + return false; +} + +// ───────────────────────────────────────────────────────────────────────────── +// checkStmt +// ───────────────────────────────────────────────────────────────────────────── + +void TypeChecker::checkStmt(ASTNode* node) { + if (!node) return; + + switch (node->kind) { + + case ASTKind::Block: + for (ASTNode* child : node->getChildren()) checkStmt(child); + break; + + case ASTKind::VariableDecl: { + auto* vd = (VariableDeclNode*)node; + Type targetType = Type::fromName(vd->varType); + if (vd->initExpr) { + Type srcType = checkExpr(vd->initExpr, targetType); + bool isLit = vd->initExpr->kind == ASTKind::Literal; + checkAssign(targetType, srcType, isLit, vd->loc, vd->name); + } + // sibling VariableDecl'ler (int a, b;) + for (ASTNode* sib : vd->getChildren()) { + if (sib->kind == ASTKind::VariableDecl) checkStmt(sib); + } + break; + } + + case ASTKind::ExpressionStatement: { + auto* es = (ExpressionStatementNode*)node; + if (es->expression) checkExpr(es->expression); + break; + } + + case ASTKind::ReturnStatement: { + auto* rs = (ReturnStatementNode*)node; + if (!rs->value) { + if (inFunction_ && !currentReturnType_.isVoid()) + diag_.report("E006", rs->loc, + "Değersiz return; fonksiyon " + + currentReturnType_.toString() + " döndürmeli"); + break; + } + Type valType = checkExpr(rs->value, currentReturnType_); + bool isLit = rs->value->kind == ASTKind::Literal; + checkAssign(currentReturnType_, valType, isLit, rs->loc, "return"); + break; + } + + case ASTKind::IfStatement: { + auto* ifn = (IfStatementNode*)node; + if (ifn->condition) checkExpr(ifn->condition); + if (ifn->thenBranch) checkStmt(ifn->thenBranch); + if (ifn->elseBranch) checkStmt(ifn->elseBranch); + break; + } + + case ASTKind::WhileStatement: { + auto* ws = (WhileStatementNode*)node; + if (ws->condition) checkExpr(ws->condition); + if (ws->body) checkStmt(ws->body); + break; + } + + case ASTKind::ForStatement: { + auto* fs = (ForStatementNode*)node; + if (fs->init) { + if (fs->init->kind == ASTKind::VariableDecl) checkStmt(fs->init); + else checkExpr(fs->init); + } + if (fs->condition) checkExpr(fs->condition); + if (fs->update) checkExpr(fs->update); + if (fs->body) checkStmt(fs->body); + break; + } + + case ASTKind::DoWhileStatement: { + auto* dw = (DoWhileStatementNode*)node; + if (dw->body) checkStmt(dw->body); + if (dw->condition) checkExpr(dw->condition); + break; + } + + case ASTKind::BreakStatement: + case ASTKind::ContinueStatement: + break; // yapısal doğrulama StructuralValidator'ın işi + + default: + break; + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// checkExpr — tip çıkarımı + resolvedType ataması +// ───────────────────────────────────────────────────────────────────────────── + +Type TypeChecker::checkExpr(ASTNode* node, const Type& expected) { + if (!node) return Type::error(); + + Type result = Type::error(); + + switch (node->kind) { + + // ── Literal ──────────────────────────────────────────────────────────── + case ASTKind::Literal: { + auto* lit = (LiteralNode*)node; + int expRank = numericRank(expected); + + switch (lit->literalType) { + case LiteralType::INTEGER: + // Bağlam daha geniş sayısal tip ise literal o tip olarak tiplenir. + if (expRank > 0) result = expected; // float veya double bekleniyor + else result = Type::Int(); + break; + case LiteralType::FLOAT: + // float literal → double bağlamında double olur; int bağlamında E003. + if (!expected.isError() && expected.equals(Type::Double())) + result = Type::Double(); + else if (!expected.isError() && numericRank(expected) == 0) { + // int bekleniyor ama float literal: E003 + diag_.report("E003", lit->loc, + "Float literal int bağlamında kullanılamaz (veri kaybı)"); + result = Type::error(); + } else { + result = Type::Float(); + } + break; + case LiteralType::BOOLEAN: result = Type::Bool(); break; + case LiteralType::STRING: result = Type::String(); break; + default: result = Type::error(); break; + } + break; + } + + // ── Identifier ───────────────────────────────────────────────────────── + case ASTKind::Identifier: { + auto* id = (IdentifierNode*)node; + result = id->resolvedSymbol ? id->resolvedSymbol->type : Type::error(); + break; + } + + // ── BinaryExpression ─────────────────────────────────────────────────── + case ASTKind::BinaryExpression: { + auto* bin = (BinaryExpressionNode*)node; + + // Atama operatörleri + if (bin->Operator == TokenType::EQUAL || + bin->Operator == TokenType::PLUS_EQUAL || + bin->Operator == TokenType::MINUS_EQUAL || + bin->Operator == TokenType::STAR_EQUAL || + bin->Operator == TokenType::SLASH_EQUAL || + bin->Operator == TokenType::PERCENT_EQUAL) { + Type leftType = checkExpr(bin->Left); + Type rightType = checkExpr(bin->Right, leftType); + bool isLit = bin->Right && bin->Right->kind == ASTKind::Literal; + checkAssign(leftType, rightType, isLit, bin->loc, "atama"); + result = leftType; + break; + } + + // Unary (Left = nullptr): -, +, !, ~ + if (!bin->Left) { + Type rightType = checkExpr(bin->Right); + if (bin->Operator == TokenType::BANG) { + result = Type::Bool(); + } else { + result = rightType.isNumeric() ? rightType : Type::error(); + if (result.isError() && !rightType.isError()) + diag_.report("E003", bin->loc, "Sayısal olmayan operand"); + } + break; + } + + Type leftType = checkExpr(bin->Left); + Type rightType = checkExpr(bin->Right); + + // Mantıksal + if (bin->Operator == TokenType::AMPERSAND_AMPERSAND || + bin->Operator == TokenType::PIPE_PIPE) { + result = Type::Bool(); + break; + } + + // Karşılaştırma + if (bin->Operator == TokenType::EQUAL_EQUAL || + bin->Operator == TokenType::BANG_EQUAL || + bin->Operator == TokenType::LESS || + bin->Operator == TokenType::LESS_EQUAL || + bin->Operator == TokenType::GREATER || + bin->Operator == TokenType::GREATER_EQUAL) { + result = Type::Bool(); + break; + } + + // Aritmetik: +, -, *, /, % + int lRank = numericRank(leftType); + int rRank = numericRank(rightType); + + if (lRank >= 0 && rRank >= 0) { + // Aynı tip veya otomatik genişletme; sonuç daha geniş tip. + result = (lRank >= rRank) ? leftType : rightType; + } else if (!leftType.isError() && !rightType.isError()) { + diag_.report("E003", bin->loc, + "Aritmetik operatör sayısal olmayan tip: " + + leftType.toString() + " ve " + rightType.toString()); + result = Type::error(); + } else { + result = Type::error(); + } + break; + } + + // ── Call ─────────────────────────────────────────────────────────────── + case ASTKind::Call: { + auto* call = (CallExpressionNode*)node; + Type calleeType = checkExpr(call->callee); + + if (!calleeType.isFunction()) { + if (!calleeType.isError()) + diag_.report("E003", call->loc, + "Çağrılabilir değil: " + calleeType.toString()); + result = Type::error(); + // Argümanları yine de gez (cascade hatayı önle) + for (auto* arg : call->arguments) checkExpr(arg); + break; + } + + // Argüman sayısı kontrolü — builtin print hariç (paramTypes boş = değişken arity) + if (!calleeType.paramTypes.empty()) { + size_t expected_count = calleeType.paramTypes.size(); + size_t got_count = call->arguments.size(); + if (got_count != expected_count) { + diag_.report("E008", call->loc, + std::to_string(expected_count) + " argüman bekleniyor, " + + std::to_string(got_count) + " verildi"); + } + } + + // Argüman tiplerini kontrol et + for (size_t i = 0; i < call->arguments.size(); ++i) { + Type paramType = (i < calleeType.paramTypes.size()) + ? calleeType.paramTypes[i] + : Type::error(); + Type argType = checkExpr(call->arguments[i], paramType); + bool isLit = call->arguments[i]->kind == ASTKind::Literal; + if (!paramType.isError()) + checkAssign(paramType, argType, isLit, + call->arguments[i]->loc, "argüman"); + } + + result = calleeType.returnType ? *calleeType.returnType : Type::Void(); + break; + } + + // ── Postfix ++/-- ────────────────────────────────────────────────────── + case ASTKind::Postfix: { + auto* pf = (PostfixNode*)node; + Type opType = checkExpr(pf->operand); + if (!opType.isNumeric() && !opType.isError()) + diag_.report("E003", pf->loc, + "++ / -- sayısal olmayan tip: " + opType.toString()); + result = opType; + break; + } + + // ── MemberAccess / IndexExpression ───────────────────────────────────── + case ASTKind::MemberAccess: { + auto* ma = (MemberAccessNode*)node; + checkExpr(ma->object); + result = Type::error(); // TODO(faz3+): struct alan çözümü + break; + } + case ASTKind::IndexExpression: { + auto* ie = (IndexExpressionNode*)node; + checkExpr(ie->object); + if (ie->index) checkExpr(ie->index); + result = Type::error(); // TODO(faz3+): array eleman tipi + break; + } + + default: + result = Type::error(); + break; + } + + // resolvedType'a yaz (ExpressionNode'dan türeyen tüm node'lar için) + if (auto* exprNode = dynamic_cast(node)) + exprNode->resolvedType = result; + + return result; +} diff --git a/src/semantic/type_checker.hpp b/src/semantic/type_checker.hpp new file mode 100644 index 0000000..cc7febf --- /dev/null +++ b/src/semantic/type_checker.hpp @@ -0,0 +1,41 @@ +#ifndef SAQUT_SEMANTIC_TYPE_CHECKER +#define SAQUT_SEMANTIC_TYPE_CHECKER + +#include "symbol/symbol_table.hpp" +#include "diagnostic/diagnostic_engine.hpp" +#include "parser/ast_node.hpp" +#include "core/type.hpp" + +class TypeChecker { +public: + TypeChecker(SymbolTable& table, DiagnosticEngine& diag) + : table_(table), diag_(diag) {} + + void check(ASTNode* program); + +private: + // İfadeyi gez, resolvedType ata, tipi döndür. + // expected: bağlam tipi — literal genişletme kararı için. + Type checkExpr(ASTNode* node, const Type& expected = Type::error()); + + void checkStmt(ASTNode* node); + void checkFunction(ASTNode* fnNode); + + // Atama / parametre uyumu: true = geçerli (uyarı dahil). + // srcIsLiteral: RHS doğrudan bir Literal node'u mu? + bool checkAssign(const Type& target, const Type& src, + bool srcIsLiteral, + const SourceLocation& loc, + const std::string& context); + + // İki sayısal tipin genişlik sırası: int=0, float=1, double=2; -1 = sayısal değil. + static int numericRank(const Type& t); + + SymbolTable& table_; + DiagnosticEngine& diag_; + + Type currentReturnType_; // aktif fonksiyonun beklenen dönüş tipi + bool inFunction_ = false; +}; + +#endif // SAQUT_SEMANTIC_TYPE_CHECKER