feat(faz3): semantik analiz — tip denetimi + yapısal doğrulama (#72)
- src/semantic/type_checker: her ExpressionNode'a resolvedType atar * literal bağlama-göre tiplenir (float x = 1 → uyarısız) * değişken→değişken genişletme → W004 * daraltma / farklı tip → E003 * fonksiyon çağrısı argüman sayısı/tipi → E008 * return tipi uyumsuzluğu → E003 (checkAssign üzerinden) - src/semantic/structural_validator: kontrol akışı kuralları * break/continue döngü dışı → E004 * return fonksiyon dışı → E005 - W004 diagnostic kataloğa eklendi - saqut check komutu: tokenize→parse→collect→typecheck→validate - examples/semantic/: widening, narrowing, bad_return, break_outside, bad_args - MISSION-FAZ3.md eklendi Doğrulama: fibonacci.sqt → 0 hata 0 uyarı ✓ widening.sqt → W004 ✓ narrowing.sqt → E003 ✓ break_outside.sqt → E004 ✓ bad_args.sqt → E008 ✓ tests/run.sh → TUM TESTLER GECTI ✓ Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c69a9b363d
commit
d9b023c550
|
|
@ -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)
|
||||
Binary file not shown.
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
// E003 — return tipi uyuşmazlığı
|
||||
int foo() {
|
||||
return 1.5;
|
||||
}
|
||||
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
// E004 — break döngü dışında
|
||||
int main() {
|
||||
break;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
// E003 — daraltma: float literal → int
|
||||
int main() {
|
||||
int x = 1.5;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
#ifndef SAQUT_CLI_CHECK
|
||||
#define SAQUT_CLI_CHECK
|
||||
|
||||
#include <iostream>
|
||||
#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
|
||||
|
|
@ -127,6 +127,7 @@ inline const std::vector<DiagInfo>& 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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<ExpressionNode*>(node))
|
||||
exprNode->resolvedType = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue