Initial commit
This commit is contained in:
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"aurac_lexer",
|
||||
"aurac_parser",
|
||||
"aurac_typechecker",
|
||||
"aurac_codegen",
|
||||
"aurac",
|
||||
# Future components:
|
||||
# "aurac", "aurac_parser", "aurac_typechecker", "aurac_codegen", "aura_core", "aura_std"
|
||||
]
|
||||
40
README.md
Normal file
40
README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# 🚀 Aura Programming Language
|
||||
|
||||
**Aura** to eksperymentalny, ultrawydajny język programowania stworzony z myślą o wyzwaniach inżynierii oprogramowania 2026 roku. Łączy w sobie bezwzględne bezpieczeństwo pamięci znane z Rusta, matematyczne dowody poprawności z Haskella oraz minimalistyczną, czytelną składnię inspirowaną Pythonem.
|
||||
|
||||
Kompilator Aury (`aurac`) został w całości napisany w Ruście i kompiluje kod źródłowy bezpośrednio do **LLVM IR** (Intermediate Representation), zapewniając wydajność na poziomie C/C++ przy zerowym narzucie w czasie działania programu (Zero-Cost Abstractions).
|
||||
|
||||
---
|
||||
|
||||
## ✨ Główne założenia (Filozofia Języka)
|
||||
|
||||
Aura rozwiązuje największy problem współczesnego IT: **brak zaufania do kodu**. Zamiast polegać na testach jednostkowych i Garbage Collectorze, Aura przenosi cały ciężar weryfikacji na etap kompilacji.
|
||||
|
||||
### 1. Niewidzialny Borrow Checker (Elided Affine Typing)
|
||||
Koniec z wyciekami pamięci, wskaźnikami `null` i skomplikowanymi adnotacjami czasu życia (`<'a>`). Aura używa analizy przepływu danych (Dataflow Analysis), aby automatycznie śledzić "własność" (Ownership) zmiennych. Gdy przekazujesz zmienną do funkcji, zostaje ona skonsumowana. Próba jej ponownego użycia (Use-After-Move) kończy się natychmiastowym błędem kompilacji.
|
||||
|
||||
### 2. Typy Semantyczne (Refinement Types) i Silnik Wnioskowania
|
||||
Typ `f32` to tylko informacja o rozmiarze w pamięci. W Aurze typy niosą ze sobą matematyczne gwarancje!
|
||||
Dzięki wbudowanemu weryfikatorowi (Symbolic Verifier), kompilator na etapie budowania programu udowadnia, że operacje są logicznie poprawne.
|
||||
Zamiast pisać `if czas < 0`, definiujesz typ: `type PositiveTime = f32{t | t > 0.0}`. Jeśli kompilator nie jest w stanie matematycznie udowodnić, że zmienna spełnia ten warunek, program się nie skompiluje.
|
||||
|
||||
### 3. Zorientowanie na Dane (Data-Oriented Design)
|
||||
Aura odrzuca tradycyjne programowanie obiektowe (OOP) na rzecz czystych transformacji danych. Skupia się na lokalności pamięci podręcznej (Cache Locality), co czyni ją idealną do pisania silników gier, systemów wbudowanych i oprogramowania HFT (High-Frequency Trading).
|
||||
|
||||
---
|
||||
|
||||
## 💻 Przykłady Kodu
|
||||
|
||||
### Symulacja Fizyki z Gwarancją Matematyczną
|
||||
```aura
|
||||
// Typ ograniczony: Czas musi być zawsze dodatni!
|
||||
type PositiveTime = f32{t | t > 0.0}
|
||||
|
||||
pure fn calculate_new_position(initial_x: f32, v: f32, dt: PositiveTime) -> f32:
|
||||
// Mnożenie: Zmienne 'v' i 'dt' zostają skonsumowane przez Borrow Checkera
|
||||
let displacement = v * dt
|
||||
|
||||
// Dodawanie: 'initial_x' i 'displacement' zostają skonsumowane
|
||||
let new_x = initial_x + displacement
|
||||
|
||||
return new_x
|
||||
16
ai_kernel.aura
Normal file
16
ai_kernel.aura
Normal file
@@ -0,0 +1,16 @@
|
||||
// Definiujemy matematyczne zasady dla pamięci:
|
||||
// Wymiar macierzy musi być większy od zera.
|
||||
type ValidDim = i32{d | d > 0}
|
||||
|
||||
// Indeks wątku na GPU (thread_id) nie może być ujemny.
|
||||
type SafeIndex = i32{i | i >= 0}
|
||||
|
||||
// Jądro GPU: Czysta funkcja, brak globalnego stanu, brak wyścigów danych!
|
||||
gpu fn matmul_step(weight: f32, activation: f32, current_sum: f32) -> f32:
|
||||
// Niewidzialny Borrow Checker konsumuje 'weight' i 'activation'
|
||||
let product = weight * activation
|
||||
|
||||
// Konsumujemy 'current_sum' i 'product'
|
||||
let new_sum = current_sum + product
|
||||
|
||||
return new_sum
|
||||
10
aurac/Cargo.toml
Normal file
10
aurac/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "aurac"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
aurac_lexer = { path = "../aurac_lexer" }
|
||||
aurac_parser = { path = "../aurac_parser" }
|
||||
aurac_typechecker = { path = "../aurac_typechecker" }
|
||||
aurac_codegen = { path = "../aurac_codegen" }
|
||||
66
aurac/src/main.rs
Normal file
66
aurac/src/main.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::process;
|
||||
|
||||
use aurac_lexer::lexer::Lexer;
|
||||
use aurac_lexer::token::TokenKind;
|
||||
use aurac_parser::parser::Parser;
|
||||
use aurac_parser::ast::{Program, Decl};
|
||||
use aurac_typechecker::checker::TypeChecker;
|
||||
use aurac_codegen::ir_gen::IrGenerator;
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() < 2 {
|
||||
eprintln!("Usage: aurac <file.aura>");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
let file_path = &args[1];
|
||||
let source_code = fs::read_to_string(file_path).unwrap_or_else(|err| {
|
||||
eprintln!("Error reading file '{}': {}", file_path, err);
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
// 1. Lexer Phase
|
||||
let mut lexer = Lexer::new(&source_code);
|
||||
let mut tokens = Vec::new();
|
||||
loop {
|
||||
let tok = lexer.next_token();
|
||||
let is_eof = tok.kind == TokenKind::Eof;
|
||||
tokens.push(tok);
|
||||
if is_eof {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Parser Phase
|
||||
let mut parser = Parser::new(&tokens);
|
||||
let fn_decl = parser.parse_fn_decl().unwrap_or_else(|err| {
|
||||
eprintln!("Syntax Error: {}", err);
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
let program = Program {
|
||||
decls: vec![Decl::Fn(fn_decl)],
|
||||
};
|
||||
|
||||
// 3. Semantic Analysis / Typechecker Phase
|
||||
let mut checker = TypeChecker::new();
|
||||
if let Err(err) = checker.check_program(&program) {
|
||||
eprintln!("Type Error: {}", err);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
// 4. Code Generation Phase (LLVM IR)
|
||||
let mut generator = IrGenerator::new();
|
||||
let ir_code = generator.generate_program(&program);
|
||||
|
||||
// 5. Output
|
||||
println!("{}", ir_code);
|
||||
|
||||
if let Err(err) = fs::write("output.ll", &ir_code) {
|
||||
eprintln!("Error writing 'output.ll': {}", err);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
7
aurac_codegen/Cargo.toml
Normal file
7
aurac_codegen/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "aurac_codegen"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
aurac_parser = { path = "../aurac_parser" }
|
||||
141
aurac_codegen/src/ir_gen.rs
Normal file
141
aurac_codegen/src/ir_gen.rs
Normal file
@@ -0,0 +1,141 @@
|
||||
use aurac_parser::ast::{Program, Decl, FnDecl, Block, Stmt, Expr, BinaryOp, TypeExpr};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct IrGenerator {
|
||||
pub output: String,
|
||||
pub tmp_counter: usize,
|
||||
pub env: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl IrGenerator {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
output: String::new(),
|
||||
tmp_counter: 0,
|
||||
env: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_program(&mut self, program: &Program) -> String {
|
||||
for decl in &program.decls {
|
||||
if let Decl::Fn(fn_decl) = decl {
|
||||
self.generate_fn(fn_decl);
|
||||
}
|
||||
}
|
||||
self.output.clone()
|
||||
}
|
||||
|
||||
fn map_type(aura_type: &str) -> &'static str {
|
||||
match aura_type {
|
||||
"f32" | "f64" | "PositiveTime" => "float",
|
||||
"i32" | "i64" | "u32" | "u64" | "i8" | "i16" | "u8" | "u16" => "i32",
|
||||
"bool" => "i1",
|
||||
_ => "unknown_type",
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_fn(&mut self, decl: &FnDecl) {
|
||||
self.env.clear();
|
||||
self.tmp_counter = 0;
|
||||
|
||||
let ret_type_str = match &decl.return_type {
|
||||
TypeExpr::BaseType(bt) => bt.clone(),
|
||||
_ => "f32".to_string(), // fallback
|
||||
};
|
||||
let llvm_ret_type = Self::map_type(&ret_type_str);
|
||||
|
||||
if decl.is_gpu {
|
||||
self.output.push_str(&format!("define ptx_kernel {} @{}(", llvm_ret_type, decl.name));
|
||||
} else {
|
||||
self.output.push_str(&format!("define {} @{}(", llvm_ret_type, decl.name));
|
||||
}
|
||||
|
||||
for (i, param) in decl.params.iter().enumerate() {
|
||||
let param_type = match ¶m.ty {
|
||||
TypeExpr::BaseType(bt) => bt.clone(),
|
||||
_ => "f32".to_string(),
|
||||
};
|
||||
let llvm_param_type = Self::map_type(¶m_type);
|
||||
|
||||
self.output.push_str(&format!("{} %{}", llvm_param_type, param.name));
|
||||
|
||||
if i < decl.params.len() - 1 {
|
||||
self.output.push_str(", ");
|
||||
}
|
||||
}
|
||||
|
||||
if decl.is_gpu {
|
||||
self.output.push_str(") #0 {\nentry:\n");
|
||||
} else {
|
||||
self.output.push_str(") {\nentry:\n");
|
||||
}
|
||||
|
||||
self.generate_block(&decl.body, &ret_type_str);
|
||||
|
||||
self.output.push_str("}\n\n");
|
||||
|
||||
if decl.is_gpu {
|
||||
self.output.push_str("attributes #0 = { \"target-cpu\"=\"sm_70\" \"target-features\"=\"+ptx60\" }\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_block(&mut self, block: &Block, expected_ret_type: &str) {
|
||||
for stmt in &block.statements {
|
||||
self.generate_stmt(stmt, expected_ret_type);
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_stmt(&mut self, stmt: &Stmt, fn_ret_type: &str) {
|
||||
match stmt {
|
||||
Stmt::Return(expr) => {
|
||||
let val_reg = self.generate_expr(expr, fn_ret_type);
|
||||
let llvm_type = Self::map_type(fn_ret_type);
|
||||
self.output.push_str(&format!(" ret {} {}\n", llvm_type, val_reg));
|
||||
}
|
||||
Stmt::ExprStmt(expr) => {
|
||||
self.generate_expr(expr, fn_ret_type);
|
||||
}
|
||||
Stmt::LetBind(name, expr) => {
|
||||
// All test vars are f32 mathematically in this scenario
|
||||
let val_reg = self.generate_expr(expr, fn_ret_type);
|
||||
self.env.insert(name.clone(), val_reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_expr(&mut self, expr: &Expr, expected_type: &str) -> String {
|
||||
match expr {
|
||||
Expr::Identifier(name) => {
|
||||
self.env.get(name).cloned().unwrap_or_else(|| format!("%{}", name))
|
||||
}
|
||||
Expr::Literal(val) => val.clone(),
|
||||
Expr::Binary(left, op, right) => {
|
||||
let left_val = self.generate_expr(left, expected_type);
|
||||
let right_val = self.generate_expr(right, expected_type);
|
||||
|
||||
let is_float = expected_type == "f32" || expected_type == "f64" || expected_type == "PositiveTime";
|
||||
let llvm_type = Self::map_type(expected_type);
|
||||
|
||||
let res_reg = format!("%{}", self.tmp_counter);
|
||||
self.tmp_counter += 1;
|
||||
|
||||
let instruction = match op {
|
||||
BinaryOp::Add => if is_float { "fadd" } else { "add" },
|
||||
BinaryOp::Sub => if is_float { "fsub" } else { "sub" },
|
||||
BinaryOp::Mul => if is_float { "fmul" } else { "mul" },
|
||||
BinaryOp::Div => if is_float { "fdiv" } else { "sdiv" },
|
||||
BinaryOp::Gt => if is_float { "fcmp ogt" } else { "icmp sgt" },
|
||||
BinaryOp::Lt => if is_float { "fcmp olt" } else { "icmp slt" },
|
||||
BinaryOp::Eq => if is_float { "fcmp oeq" } else { "icmp eq" },
|
||||
};
|
||||
|
||||
self.output.push_str(&format!(
|
||||
" {} = {} {} {}, {}\n",
|
||||
res_reg, instruction, llvm_type, left_val, right_val
|
||||
));
|
||||
|
||||
res_reg
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
aurac_codegen/src/lib.rs
Normal file
37
aurac_codegen/src/lib.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
pub mod ir_gen;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ir_gen::IrGenerator;
|
||||
use aurac_parser::ast::{Program, Decl, FnDecl, Param, TypeExpr, Block, Stmt, Expr, BinaryOp};
|
||||
|
||||
#[test]
|
||||
fn test_generate_add_fn() {
|
||||
let program = Program {
|
||||
decls: vec![Decl::Fn(FnDecl {
|
||||
is_pure: true,
|
||||
is_gpu: false,
|
||||
name: "add".to_string(),
|
||||
params: vec![
|
||||
Param { name: "a".to_string(), ty: TypeExpr::BaseType("f32".to_string()) },
|
||||
Param { name: "b".to_string(), ty: TypeExpr::BaseType("f32".to_string()) },
|
||||
],
|
||||
return_type: TypeExpr::BaseType("f32".to_string()),
|
||||
body: Block {
|
||||
statements: vec![Stmt::Return(Expr::Binary(
|
||||
Box::new(Expr::Identifier("a".to_string())),
|
||||
BinaryOp::Add,
|
||||
Box::new(Expr::Identifier("b".to_string())),
|
||||
))],
|
||||
},
|
||||
})],
|
||||
};
|
||||
|
||||
let mut generator = IrGenerator::new();
|
||||
let ir = generator.generate_program(&program);
|
||||
|
||||
assert!(ir.contains("define float @add(float %a, float %b)"));
|
||||
assert!(ir.contains("fadd float %a, %b"));
|
||||
assert!(ir.contains("ret float %0"));
|
||||
}
|
||||
}
|
||||
6
aurac_lexer/Cargo.toml
Normal file
6
aurac_lexer/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "aurac_lexer"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
203
aurac_lexer/src/lexer.rs
Normal file
203
aurac_lexer/src/lexer.rs
Normal file
@@ -0,0 +1,203 @@
|
||||
use crate::token::{Span, Token, TokenKind};
|
||||
use std::str::CharIndices;
|
||||
|
||||
pub struct Lexer<'a> {
|
||||
input: &'a str,
|
||||
chars: CharIndices<'a>,
|
||||
current: Option<(usize, char)>,
|
||||
line: usize,
|
||||
column: usize,
|
||||
indent_stack: Vec<usize>,
|
||||
pending_dedents: usize,
|
||||
emitted_eof: bool,
|
||||
}
|
||||
|
||||
impl<'a> Lexer<'a> {
|
||||
pub fn new(input: &'a str) -> Self {
|
||||
let mut chars = input.char_indices();
|
||||
let current = chars.next();
|
||||
Self {
|
||||
input,
|
||||
chars,
|
||||
current,
|
||||
line: 1,
|
||||
column: 1,
|
||||
indent_stack: vec![0], // base level indentation
|
||||
pending_dedents: 0,
|
||||
emitted_eof: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn advance(&mut self) -> Option<(usize, char)> {
|
||||
let result = self.current;
|
||||
if let Some((_, c)) = result {
|
||||
if c == '\n' {
|
||||
self.line += 1;
|
||||
self.column = 1;
|
||||
} else {
|
||||
self.column += 1;
|
||||
}
|
||||
}
|
||||
self.current = self.chars.next();
|
||||
result
|
||||
}
|
||||
|
||||
fn peek(&self) -> Option<(usize, char)> {
|
||||
self.current
|
||||
}
|
||||
|
||||
pub fn next_token(&mut self) -> Token<'a> {
|
||||
if self.pending_dedents > 0 {
|
||||
self.pending_dedents -= 1;
|
||||
return Token::new(
|
||||
TokenKind::Dedent,
|
||||
Span { line: self.line, column: 1, offset: self.current.map(|(o,_)| o).unwrap_or(self.input.len()), len: 0 }
|
||||
);
|
||||
}
|
||||
|
||||
if let Some((offset, c)) = self.current {
|
||||
// Indentation mapping at the start of lines
|
||||
if self.column == 1 && c != '\n' && c != '\r' {
|
||||
let mut spaces = 0;
|
||||
while let Some((_, pc)) = self.current {
|
||||
if pc == ' ' {
|
||||
spaces += 1;
|
||||
self.advance();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if self.current.map_or(false, |(_, c)| c == '\n' || c == '\r') {
|
||||
// Empty/whitespace-only line: proceed to standard token matching
|
||||
// which will hit the '\n' matcher below.
|
||||
} else {
|
||||
let current_indent = *self.indent_stack.last().unwrap_or(&0);
|
||||
|
||||
if spaces > current_indent {
|
||||
self.indent_stack.push(spaces);
|
||||
return Token::new(
|
||||
TokenKind::Indent,
|
||||
Span { line: self.line, column: 1, offset, len: spaces }
|
||||
);
|
||||
} else if spaces < current_indent {
|
||||
let mut dedents = 0;
|
||||
while let Some(&last) = self.indent_stack.last() {
|
||||
if last > spaces {
|
||||
self.indent_stack.pop();
|
||||
dedents += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if dedents > 0 {
|
||||
self.pending_dedents = dedents - 1;
|
||||
return Token::new(
|
||||
TokenKind::Dedent,
|
||||
Span { line: self.line, column: 1, offset, len: spaces }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normal matching path
|
||||
let (start_offset, c) = self.advance().unwrap();
|
||||
let start_col = self.column - 1;
|
||||
|
||||
match c {
|
||||
' ' | '\r' => self.next_token(),
|
||||
'\n' => Token::new(TokenKind::Newline, Span { line: self.line - 1, column: start_col, offset: start_offset, len: 1 }),
|
||||
':' => {
|
||||
if self.peek().map(|(_, pc)| pc) == Some(':') {
|
||||
let _ = self.advance();
|
||||
Token::new(TokenKind::DoubleColon, Span { line: self.line, column: start_col, offset: start_offset, len: 2 })
|
||||
} else {
|
||||
Token::new(TokenKind::Colon, Span { line: self.line, column: start_col, offset: start_offset, len: 1 })
|
||||
}
|
||||
}
|
||||
'-' => {
|
||||
if self.peek().map(|(_, pc)| pc) == Some('>') {
|
||||
let _ = self.advance();
|
||||
Token::new(TokenKind::Arrow, Span { line: self.line, column: start_col, offset: start_offset, len: 2 })
|
||||
} else {
|
||||
Token::new(TokenKind::Minus, Span { line: self.line, column: start_col, offset: start_offset, len: 1 })
|
||||
}
|
||||
}
|
||||
'+' => Token::new(TokenKind::Plus, Span { line: self.line, column: start_col, offset: start_offset, len: 1 }),
|
||||
'*' => Token::new(TokenKind::Star, Span { line: self.line, column: start_col, offset: start_offset, len: 1 }),
|
||||
'/' => Token::new(TokenKind::Slash, Span { line: self.line, column: start_col, offset: start_offset, len: 1 }),
|
||||
'(' => Token::new(TokenKind::OpenParen, Span { line: self.line, column: start_col, offset: start_offset, len: 1 }),
|
||||
')' => Token::new(TokenKind::CloseParen, Span { line: self.line, column: start_col, offset: start_offset, len: 1 }),
|
||||
'{' => Token::new(TokenKind::OpenBrace, Span { line: self.line, column: start_col, offset: start_offset, len: 1 }),
|
||||
'}' => Token::new(TokenKind::CloseBrace, Span { line: self.line, column: start_col, offset: start_offset, len: 1 }),
|
||||
',' => Token::new(TokenKind::Comma, Span { line: self.line, column: start_col, offset: start_offset, len: 1 }),
|
||||
'=' => {
|
||||
if self.peek().map(|(_, pc)| pc) == Some('=') {
|
||||
let _ = self.advance();
|
||||
Token::new(TokenKind::EqualEqual, Span { line: self.line, column: start_col, offset: start_offset, len: 2 })
|
||||
} else {
|
||||
Token::new(TokenKind::Equal, Span { line: self.line, column: start_col, offset: start_offset, len: 1 })
|
||||
}
|
||||
}
|
||||
'|' => Token::new(TokenKind::Pipe, Span { line: self.line, column: start_col, offset: start_offset, len: 1 }),
|
||||
'>' => Token::new(TokenKind::Greater, Span { line: self.line, column: start_col, offset: start_offset, len: 1 }),
|
||||
'<' => Token::new(TokenKind::Less, Span { line: self.line, column: start_col, offset: start_offset, len: 1 }),
|
||||
_ if c.is_alphabetic() => {
|
||||
while let Some((_, pc)) = self.peek() {
|
||||
if pc.is_alphanumeric() || pc == '_' {
|
||||
self.advance();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let end_offset = self.current.map(|(o, _)| o).unwrap_or(self.input.len());
|
||||
let ident_str = &self.input[start_offset..end_offset];
|
||||
|
||||
let kind = match ident_str {
|
||||
"struct" => TokenKind::Struct,
|
||||
"fn" => TokenKind::Fn,
|
||||
"pure" => TokenKind::Pure,
|
||||
"actor" => TokenKind::Actor,
|
||||
"let" => TokenKind::Let,
|
||||
"if" => TokenKind::If,
|
||||
"else" => TokenKind::Else,
|
||||
"match" => TokenKind::Match,
|
||||
"return" => TokenKind::Return,
|
||||
"type" => TokenKind::Type,
|
||||
"gpu" => TokenKind::Gpu,
|
||||
"i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" | "f32" | "f64" | "bool" | "str" => TokenKind::BaseType(ident_str),
|
||||
_ => TokenKind::Ident(ident_str),
|
||||
};
|
||||
Token::new(kind, Span { line: self.line, column: start_col, offset: start_offset, len: end_offset - start_offset })
|
||||
}
|
||||
_ if c.is_ascii_digit() => {
|
||||
while let Some((_, pc)) = self.peek() {
|
||||
if pc.is_ascii_digit() || pc == '.' {
|
||||
self.advance();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let end_offset = self.current.map(|(o, _)| o).unwrap_or(self.input.len());
|
||||
let num_str = &self.input[start_offset..end_offset];
|
||||
Token::new(TokenKind::Number(num_str), Span { line: self.line, column: start_col, offset: start_offset, len: end_offset - start_offset })
|
||||
}
|
||||
_ => Token::new(TokenKind::Error(c), Span { line: self.line, column: start_col, offset: start_offset, len: c.len_utf8() }),
|
||||
}
|
||||
} else {
|
||||
if self.indent_stack.len() > 1 {
|
||||
self.indent_stack.pop();
|
||||
Token::new(TokenKind::Dedent, Span { line: self.line, column: self.column, offset: self.input.len(), len: 0 })
|
||||
} else if !self.emitted_eof {
|
||||
self.emitted_eof = true;
|
||||
Token::new(TokenKind::Eof, Span { line: self.line, column: self.column, offset: self.input.len(), len: 0 })
|
||||
} else {
|
||||
Token::new(TokenKind::Eof, Span { line: self.line, column: self.column, offset: self.input.len(), len: 0 })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
aurac_lexer/src/lib.rs
Normal file
43
aurac_lexer/src/lib.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
pub mod lexer;
|
||||
pub mod token;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::lexer::Lexer;
|
||||
use super::token::TokenKind::*;
|
||||
|
||||
#[test]
|
||||
fn test_struct_indentation() {
|
||||
let input = "struct Position:\n x: f32\n y: f32\n";
|
||||
let mut lexer = Lexer::new(input);
|
||||
|
||||
let expected_tokens = vec![
|
||||
Struct,
|
||||
Ident("Position"),
|
||||
Colon,
|
||||
Newline,
|
||||
Indent,
|
||||
Ident("x"),
|
||||
Colon,
|
||||
BaseType("f32"),
|
||||
Newline,
|
||||
Ident("y"),
|
||||
Colon,
|
||||
BaseType("f32"),
|
||||
Newline,
|
||||
Dedent,
|
||||
Eof,
|
||||
];
|
||||
|
||||
let mut actual_tokens = Vec::new();
|
||||
loop {
|
||||
let tok = lexer.next_token();
|
||||
actual_tokens.push(tok.kind.clone());
|
||||
if tok.kind == Eof {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(actual_tokens, expected_tokens);
|
||||
}
|
||||
}
|
||||
45
aurac_lexer/src/token.rs
Normal file
45
aurac_lexer/src/token.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Span {
|
||||
pub line: usize,
|
||||
pub column: usize,
|
||||
pub offset: usize,
|
||||
pub len: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum TokenKind<'a> {
|
||||
// Keywords
|
||||
Struct, Fn, Pure, Actor, Let, If, Else, Match, Return, Type, Gpu,
|
||||
|
||||
// Identifiers and Literals bound to the input lifetime ('a) for zero-copy
|
||||
Ident(&'a str),
|
||||
Number(&'a str),
|
||||
StringLit(&'a str),
|
||||
|
||||
// Base Types
|
||||
BaseType(&'a str),
|
||||
|
||||
// Symbols & Operators
|
||||
Colon, DoubleColon, Comma, Arrow, Equal, Pipe,
|
||||
Plus, Minus, Star, Slash,
|
||||
OpenParen, CloseParen, OpenBrace, CloseBrace, OpenAngle, CloseAngle,
|
||||
Greater, Less, EqualEqual,
|
||||
|
||||
// Significant Whitespace
|
||||
Indent, Dedent, Newline,
|
||||
|
||||
Eof,
|
||||
Error(char),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Token<'a> {
|
||||
pub kind: TokenKind<'a>,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl<'a> Token<'a> {
|
||||
pub fn new(kind: TokenKind<'a>, span: Span) -> Self {
|
||||
Self { kind, span }
|
||||
}
|
||||
}
|
||||
7
aurac_parser/Cargo.toml
Normal file
7
aurac_parser/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "aurac_parser"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
aurac_lexer = { path = "../aurac_lexer" }
|
||||
75
aurac_parser/src/ast.rs
Normal file
75
aurac_parser/src/ast.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Program {
|
||||
pub decls: Vec<Decl>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Decl {
|
||||
Struct(StructDecl),
|
||||
Fn(FnDecl),
|
||||
TypeAlias(String, TypeExpr),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct StructDecl {
|
||||
pub name: String,
|
||||
pub fields: Vec<FieldDecl>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FieldDecl {
|
||||
pub name: String,
|
||||
pub ty: TypeExpr,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FnDecl {
|
||||
pub is_pure: bool,
|
||||
pub is_gpu: bool,
|
||||
pub name: String,
|
||||
pub params: Vec<Param>,
|
||||
pub return_type: TypeExpr,
|
||||
pub body: Block,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Param {
|
||||
pub name: String,
|
||||
pub ty: TypeExpr,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Block {
|
||||
pub statements: Vec<Stmt>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Stmt {
|
||||
Return(Expr),
|
||||
ExprStmt(Expr),
|
||||
LetBind(String, Expr),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Expr {
|
||||
Binary(Box<Expr>, BinaryOp, Box<Expr>),
|
||||
Literal(String),
|
||||
Identifier(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum BinaryOp {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
Gt,
|
||||
Lt,
|
||||
Eq,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum TypeExpr {
|
||||
BaseType(String),
|
||||
Refined(Box<TypeExpr>, String, Box<Expr>),
|
||||
}
|
||||
87
aurac_parser/src/lib.rs
Normal file
87
aurac_parser/src/lib.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
pub mod ast;
|
||||
pub mod parser;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::parser::Parser;
|
||||
use super::ast::{StructDecl, FieldDecl, TypeExpr, FnDecl, Param, Block, Stmt, Expr, BinaryOp};
|
||||
|
||||
#[test]
|
||||
fn test_parse_pure_fn() {
|
||||
let input = "pure fn add(a: f32, b: f32) -> f32:\n return a + b\n";
|
||||
let mut lexer = Lexer::new(input);
|
||||
|
||||
let mut tokens = Vec::new();
|
||||
loop {
|
||||
let tok = lexer.next_token();
|
||||
let is_eof = tok.kind == TokenKind::Eof;
|
||||
tokens.push(tok);
|
||||
if is_eof {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut parser = Parser::new(&tokens);
|
||||
let fn_decl = parser.parse_fn_decl().expect("Failed to parse pure fn decl");
|
||||
|
||||
let expected = FnDecl {
|
||||
is_pure: true,
|
||||
is_gpu: false,
|
||||
name: "add".to_string(),
|
||||
params: vec![
|
||||
Param { name: "a".to_string(), ty: TypeExpr::BaseType("f32".to_string()) },
|
||||
Param { name: "b".to_string(), ty: TypeExpr::BaseType("f32".to_string()) },
|
||||
],
|
||||
return_type: TypeExpr::BaseType("f32".to_string()),
|
||||
body: Block {
|
||||
statements: vec![
|
||||
Stmt::Return(Expr::Binary(
|
||||
Box::new(Expr::Identifier("a".to_string())),
|
||||
BinaryOp::Add,
|
||||
Box::new(Expr::Identifier("b".to_string())),
|
||||
))
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(fn_decl, expected);
|
||||
}
|
||||
use aurac_lexer::lexer::Lexer;
|
||||
use aurac_lexer::token::TokenKind;
|
||||
|
||||
#[test]
|
||||
fn test_parse_struct_decl() {
|
||||
let input = "struct Position:\n x: f32\n y: f32\n";
|
||||
let mut lexer = Lexer::new(input);
|
||||
|
||||
// Exhaust the lexer to contiguous slice to mimic real pipelines
|
||||
let mut tokens = Vec::new();
|
||||
loop {
|
||||
let tok = lexer.next_token();
|
||||
let is_eof = tok.kind == TokenKind::Eof;
|
||||
tokens.push(tok);
|
||||
if is_eof {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut parser = Parser::new(&tokens);
|
||||
let struct_decl = parser.parse_struct_decl().expect("Failed to parse struct decl");
|
||||
|
||||
let expected = StructDecl {
|
||||
name: "Position".to_string(),
|
||||
fields: vec![
|
||||
FieldDecl {
|
||||
name: "x".to_string(),
|
||||
ty: TypeExpr::BaseType("f32".to_string()),
|
||||
},
|
||||
FieldDecl {
|
||||
name: "y".to_string(),
|
||||
ty: TypeExpr::BaseType("f32".to_string()),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
assert_eq!(struct_decl, expected);
|
||||
}
|
||||
}
|
||||
278
aurac_parser/src/parser.rs
Normal file
278
aurac_parser/src/parser.rs
Normal file
@@ -0,0 +1,278 @@
|
||||
use aurac_lexer::token::{Token, TokenKind};
|
||||
use crate::ast::*;
|
||||
|
||||
pub struct Parser<'a> {
|
||||
tokens: &'a [Token<'a>],
|
||||
current: usize,
|
||||
}
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
pub fn new(tokens: &'a [Token<'a>]) -> Self {
|
||||
Self { tokens, current: 0 }
|
||||
}
|
||||
|
||||
fn peek(&self) -> Option<&Token<'a>> {
|
||||
self.tokens.get(self.current)
|
||||
}
|
||||
|
||||
fn advance(&mut self) -> Option<&Token<'a>> {
|
||||
let curr = self.peek();
|
||||
self.current += 1;
|
||||
curr
|
||||
}
|
||||
|
||||
fn expect(&mut self, expected: TokenKind<'a>) -> Result<(), String> {
|
||||
let peeked = self.peek();
|
||||
match peeked {
|
||||
Some(curr) if curr.kind == expected => {
|
||||
self.advance();
|
||||
Ok(())
|
||||
},
|
||||
Some(curr) => Err(format!("Expected {:?}, found {:?}", expected, curr.kind)),
|
||||
None => Err(format!("Expected {:?}, found EOF", expected)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_struct_decl(&mut self) -> Result<StructDecl, String> {
|
||||
self.expect(TokenKind::Struct)?;
|
||||
|
||||
let name = match self.advance() {
|
||||
Some(Token { kind: TokenKind::Ident(id), .. }) => id.to_string(),
|
||||
Some(t) => return Err(format!("Expected identifier after struct, found {:?}", t.kind)),
|
||||
None => return Err("Expected identifier after struct, found EOF".to_string()),
|
||||
};
|
||||
|
||||
self.expect(TokenKind::Colon)?;
|
||||
self.expect(TokenKind::Newline)?;
|
||||
self.expect(TokenKind::Indent)?;
|
||||
|
||||
let mut fields = Vec::new();
|
||||
loop {
|
||||
// Ignore leading blank lines inside struct bodies
|
||||
while let Some(Token { kind: TokenKind::Newline, .. }) = self.peek() {
|
||||
self.advance();
|
||||
}
|
||||
|
||||
match self.peek() {
|
||||
Some(Token { kind: TokenKind::Dedent, .. }) => {
|
||||
self.advance();
|
||||
break;
|
||||
}
|
||||
Some(Token { kind: TokenKind::Ident(_), .. }) => {
|
||||
let field_name = if let Token { kind: TokenKind::Ident(id), .. } = self.advance().unwrap() {
|
||||
id.to_string()
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
self.expect(TokenKind::Colon)?;
|
||||
let ty = self.parse_type_expr()?;
|
||||
fields.push(FieldDecl { name: field_name, ty });
|
||||
|
||||
// Fields must be terminated by Newline or immediately ended with Dedent
|
||||
match self.peek() {
|
||||
Some(Token { kind: TokenKind::Newline, .. }) => {
|
||||
self.advance();
|
||||
},
|
||||
Some(Token { kind: TokenKind::Dedent, .. }) => (),
|
||||
Some(t) => return Err(format!("Expected Newline or Dedent after field, found {:?}", t.kind)),
|
||||
None => return Err("Unexpected EOF in struct fields".to_string()),
|
||||
}
|
||||
}
|
||||
Some(t) => return Err(format!("Expected field declaration or Dedent, found {:?}", t.kind)),
|
||||
None => return Err("Unexpected EOF parsing struct".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(StructDecl { name, fields })
|
||||
}
|
||||
|
||||
pub fn parse_fn_decl(&mut self) -> Result<FnDecl, String> {
|
||||
let mut is_pure = false;
|
||||
let mut is_gpu = false;
|
||||
|
||||
if let Some(Token { kind: TokenKind::Pure, .. }) = self.peek() {
|
||||
self.advance();
|
||||
is_pure = true;
|
||||
} else if let Some(Token { kind: TokenKind::Gpu, .. }) = self.peek() {
|
||||
self.advance();
|
||||
is_gpu = true;
|
||||
is_pure = true; // GPU kernels are inherently pure
|
||||
}
|
||||
|
||||
self.expect(TokenKind::Fn)?;
|
||||
|
||||
let name = match self.advance() {
|
||||
Some(Token { kind: TokenKind::Ident(id), .. }) => id.to_string(),
|
||||
Some(t) => return Err(format!("Expected identifier after fn, found {:?}", t.kind)),
|
||||
None => return Err("Expected identifier after fn, found EOF".to_string()),
|
||||
};
|
||||
|
||||
self.expect(TokenKind::OpenParen)?;
|
||||
|
||||
let mut params = Vec::new();
|
||||
while let Some(tok) = self.peek() {
|
||||
if tok.kind == TokenKind::CloseParen {
|
||||
break;
|
||||
}
|
||||
|
||||
let param_name = match self.advance() {
|
||||
Some(Token { kind: TokenKind::Ident(id), .. }) => id.to_string(),
|
||||
Some(t) => return Err(format!("Expected parameter name, found {:?}", t.kind)),
|
||||
None => return Err("Expected parameter name, found EOF".to_string()),
|
||||
};
|
||||
|
||||
self.expect(TokenKind::Colon)?;
|
||||
let ty = self.parse_type_expr()?;
|
||||
params.push(Param { name: param_name, ty });
|
||||
|
||||
if let Some(Token { kind: TokenKind::Comma, .. }) = self.peek() {
|
||||
self.advance();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.expect(TokenKind::CloseParen)?;
|
||||
|
||||
self.expect(TokenKind::Arrow)?;
|
||||
let return_type = self.parse_type_expr()?;
|
||||
|
||||
self.expect(TokenKind::Colon)?;
|
||||
self.expect(TokenKind::Newline)?;
|
||||
|
||||
let body = self.parse_block()?;
|
||||
|
||||
Ok(FnDecl {
|
||||
is_pure,
|
||||
is_gpu,
|
||||
name,
|
||||
params,
|
||||
return_type,
|
||||
body,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_block(&mut self) -> Result<Block, String> {
|
||||
self.expect(TokenKind::Indent)?;
|
||||
|
||||
let mut statements = Vec::new();
|
||||
loop {
|
||||
while let Some(Token { kind: TokenKind::Newline, .. }) = self.peek() {
|
||||
self.advance();
|
||||
}
|
||||
|
||||
match self.peek() {
|
||||
Some(Token { kind: TokenKind::Dedent, .. }) => {
|
||||
self.advance();
|
||||
break;
|
||||
}
|
||||
Some(_) => {
|
||||
statements.push(self.parse_stmt()?);
|
||||
|
||||
match self.peek() {
|
||||
Some(Token { kind: TokenKind::Newline, .. }) => {
|
||||
self.advance();
|
||||
},
|
||||
Some(Token { kind: TokenKind::Dedent, .. }) => (),
|
||||
Some(t) => return Err(format!("Expected Newline or Dedent after statement, found {:?}", t.kind)),
|
||||
None => return Err("Unexpected EOF in block".to_string()),
|
||||
}
|
||||
}
|
||||
None => return Err("Unexpected EOF parsing block".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Block { statements })
|
||||
}
|
||||
|
||||
fn parse_stmt(&mut self) -> Result<Stmt, String> {
|
||||
if let Some(Token { kind: TokenKind::Return, .. }) = self.peek() {
|
||||
self.advance();
|
||||
let expr = self.parse_expr()?;
|
||||
self.expect(TokenKind::Newline)?;
|
||||
return Ok(Stmt::Return(expr));
|
||||
}
|
||||
|
||||
if let Some(Token { kind: TokenKind::Let, .. }) = self.peek() {
|
||||
self.advance();
|
||||
let name = match self.advance() {
|
||||
Some(Token { kind: TokenKind::Ident(id), .. }) => id.to_string(),
|
||||
Some(t) => return Err(format!("Expected identifier after 'let', found {:?}", t.kind)),
|
||||
None => return Err("Expected identifier after 'let', found EOF".to_string()),
|
||||
};
|
||||
self.expect(TokenKind::Equal)?;
|
||||
let expr = self.parse_expr()?;
|
||||
self.expect(TokenKind::Newline)?;
|
||||
return Ok(Stmt::LetBind(name, expr));
|
||||
}
|
||||
|
||||
let expr = self.parse_expr()?;
|
||||
Ok(Stmt::ExprStmt(expr))
|
||||
}
|
||||
|
||||
fn parse_expr(&mut self) -> Result<Expr, String> {
|
||||
let mut left = match self.advance() {
|
||||
Some(Token { kind: TokenKind::Ident(id), .. }) => Expr::Identifier(id.to_string()),
|
||||
Some(Token { kind: TokenKind::Number(num), .. }) => Expr::Literal(num.to_string()),
|
||||
Some(t) => return Err(format!("Expected expression, found {:?}", t.kind)),
|
||||
None => return Err("Expected expression, found EOF".to_string()),
|
||||
};
|
||||
|
||||
if let Some(tok) = self.peek() {
|
||||
let op = match tok.kind {
|
||||
TokenKind::Plus => Some(BinaryOp::Add),
|
||||
TokenKind::Minus => Some(BinaryOp::Sub),
|
||||
TokenKind::Star => Some(BinaryOp::Mul),
|
||||
TokenKind::Slash => Some(BinaryOp::Div),
|
||||
TokenKind::Greater => Some(BinaryOp::Gt),
|
||||
TokenKind::Less => Some(BinaryOp::Lt),
|
||||
TokenKind::EqualEqual => Some(BinaryOp::Eq),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(bin_op) = op {
|
||||
self.advance();
|
||||
let right = self.parse_expr()?;
|
||||
left = Expr::Binary(Box::new(left), bin_op, Box::new(right));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(left)
|
||||
}
|
||||
|
||||
pub fn parse_type_alias(&mut self) -> Result<Decl, String> {
|
||||
self.expect(TokenKind::Type)?;
|
||||
let name = match self.advance() {
|
||||
Some(Token { kind: TokenKind::Ident(id), .. }) => id.to_string(),
|
||||
Some(t) => return Err(format!("Expected identifier for type alias, found {:?}", t.kind)),
|
||||
None => return Err("Expected identifier, found EOF".to_string()),
|
||||
};
|
||||
self.expect(TokenKind::Equal)?;
|
||||
let ty = self.parse_type_expr()?;
|
||||
self.expect(TokenKind::Newline)?;
|
||||
Ok(Decl::TypeAlias(name, ty))
|
||||
}
|
||||
|
||||
fn parse_type_expr(&mut self) -> Result<TypeExpr, String> {
|
||||
let base_ty = match self.advance() {
|
||||
Some(Token { kind: TokenKind::BaseType(bt), .. }) => TypeExpr::BaseType(bt.to_string()),
|
||||
Some(Token { kind: TokenKind::Ident(id), .. }) => TypeExpr::BaseType(id.to_string()),
|
||||
Some(t) => return Err(format!("Expected type, found {:?}", t.kind)),
|
||||
None => return Err("Expected type, found EOF".to_string()),
|
||||
};
|
||||
|
||||
if let Some(Token { kind: TokenKind::OpenBrace, .. }) = self.peek() {
|
||||
self.advance();
|
||||
let var_name = match self.advance() {
|
||||
Some(Token { kind: TokenKind::Ident(id), .. }) => id.to_string(),
|
||||
_ => return Err("Expected variable identifier in refinement clause".to_string()),
|
||||
};
|
||||
self.expect(TokenKind::Pipe)?;
|
||||
let constraint = self.parse_expr()?;
|
||||
self.expect(TokenKind::CloseBrace)?;
|
||||
Ok(TypeExpr::Refined(Box::new(base_ty), var_name, Box::new(constraint)))
|
||||
} else {
|
||||
Ok(base_ty)
|
||||
}
|
||||
}
|
||||
}
|
||||
7
aurac_typechecker/Cargo.toml
Normal file
7
aurac_typechecker/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "aurac_typechecker"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
aurac_parser = { path = "../aurac_parser" }
|
||||
222
aurac_typechecker/src/checker.rs
Normal file
222
aurac_typechecker/src/checker.rs
Normal file
@@ -0,0 +1,222 @@
|
||||
use std::collections::HashSet;
|
||||
use std::collections::HashMap;
|
||||
use aurac_parser::ast::{Program, Decl, StructDecl, TypeExpr, FnDecl, Block, Stmt, Expr, BinaryOp};
|
||||
use crate::env::{SymbolTable, FunctionSignature, OwnershipState};
|
||||
use crate::symbolic::SymbolicEngine;
|
||||
|
||||
pub struct TypeChecker {
|
||||
env: SymbolTable,
|
||||
}
|
||||
|
||||
impl TypeChecker {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
env: SymbolTable::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_program(&mut self, program: &Program) -> Result<(), String> {
|
||||
// First Pass: Register global declarations
|
||||
for decl in &program.decls {
|
||||
match decl {
|
||||
Decl::TypeAlias(name, ty) => {
|
||||
self.env.register_type(name.clone());
|
||||
self.env.type_aliases.insert(name.clone(), ty.clone());
|
||||
}
|
||||
Decl::Struct(struct_decl) => {
|
||||
if !self.env.register_type(struct_decl.name.clone()) {
|
||||
return Err(format!("Duplicate type definition: {}", struct_decl.name));
|
||||
}
|
||||
}
|
||||
Decl::Fn(fn_decl) => {
|
||||
let mut param_types = Vec::new();
|
||||
for param in &fn_decl.params {
|
||||
match ¶m.ty {
|
||||
TypeExpr::BaseType(bt) => param_types.push(bt.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
let return_type = match &fn_decl.return_type {
|
||||
TypeExpr::BaseType(bt) => bt.clone(),
|
||||
};
|
||||
|
||||
let sig = FunctionSignature {
|
||||
param_types,
|
||||
return_type,
|
||||
};
|
||||
|
||||
if !self.env.register_function(fn_decl.name.clone(), sig) {
|
||||
return Err(format!("Duplicate function definition: {}", fn_decl.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second Pass: Validate bodies
|
||||
for decl in &program.decls {
|
||||
match decl {
|
||||
Decl::TypeAlias(_, _) => {}
|
||||
Decl::Struct(struct_decl) => {
|
||||
self.check_struct_decl(struct_decl)?;
|
||||
}
|
||||
Decl::Fn(fn_decl) => {
|
||||
self.check_fn_decl(fn_decl)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_struct_decl(&mut self, decl: &StructDecl) -> Result<(), String> {
|
||||
let mut seen_fields = HashSet::new();
|
||||
|
||||
for field in &decl.fields {
|
||||
if !seen_fields.insert(field.name.clone()) {
|
||||
return Err(format!("Duplicate field '{}' in struct '{}'", field.name, decl.name));
|
||||
}
|
||||
|
||||
match &field.ty {
|
||||
TypeExpr::BaseType(base_type) => {
|
||||
if !self.env.is_type_defined(base_type) {
|
||||
return Err(format!(
|
||||
"Unknown type '{}' used in field '{}' of struct '{}'",
|
||||
base_type, field.name, decl.name
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_type_expr(&self, ty: &TypeExpr) -> (String, Option<Expr>) {
|
||||
match ty {
|
||||
TypeExpr::BaseType(bt) => {
|
||||
if let Some(alias_ty) = self.env.type_aliases.get(bt) {
|
||||
self.resolve_type_expr(alias_ty)
|
||||
} else {
|
||||
(bt.clone(), None)
|
||||
}
|
||||
}
|
||||
TypeExpr::Refined(base, _, expr) => {
|
||||
let (bt, _) = self.resolve_type_expr(base);
|
||||
(bt, Some(*expr.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_fn_decl(&mut self, decl: &FnDecl) -> Result<(), String> {
|
||||
self.env.enter_scope();
|
||||
|
||||
let mut param_constraints = HashMap::new();
|
||||
|
||||
for param in &decl.params {
|
||||
let (ty_str, constraint_expr) = self.resolve_type_expr(¶m.ty);
|
||||
|
||||
if decl.is_gpu {
|
||||
// GPU kernel constraint enforcement bootstrapped layer
|
||||
match ty_str.as_str() {
|
||||
"f32" | "f64" | "i32" | "u32" | "bool" => {},
|
||||
_ => return Err(format!("GPU Kernel Error: Type '{}' is not a valid parallel primitive for parameter '{}'", ty_str, param.name)),
|
||||
}
|
||||
}
|
||||
|
||||
if !self.env.is_type_defined(&ty_str) {
|
||||
return Err(format!("Unknown type '{}' in parameter '{}'", ty_str, param.name));
|
||||
}
|
||||
|
||||
if !self.env.define_local(param.name.clone(), ty_str) {
|
||||
return Err(format!("Duplicate parameter name: {}", param.name));
|
||||
}
|
||||
|
||||
if let Some(expr) = constraint_expr {
|
||||
param_constraints.insert(param.name.clone(), expr);
|
||||
}
|
||||
}
|
||||
|
||||
let (expected_return_type, return_constraint) = self.resolve_type_expr(&decl.return_type);
|
||||
|
||||
if !self.env.is_type_defined(&expected_return_type) {
|
||||
return Err(format!("Unknown return type '{}' for function '{}'", expected_return_type, decl.name));
|
||||
}
|
||||
|
||||
self.check_block(&decl.body, &expected_return_type, return_constraint.as_ref(), ¶m_constraints)?;
|
||||
|
||||
self.env.exit_scope();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_block(&mut self, block: &Block, expected_return: &str, return_constraint: Option<&Expr>, param_constraints: &HashMap<String, Expr>) -> Result<(), String> {
|
||||
for stmt in &block.statements {
|
||||
self.check_stmt(stmt, expected_return, return_constraint, param_constraints)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_stmt(&mut self, stmt: &Stmt, expected_return: &str, return_constraint: Option<&Expr>, param_constraints: &HashMap<String, Expr>) -> Result<(), String> {
|
||||
match stmt {
|
||||
Stmt::Return(expr) => {
|
||||
let actual_type = self.evaluate_expr_type(expr)?;
|
||||
if actual_type != expected_return {
|
||||
return Err(format!("Type mismatch: expected return type '{}', but found '{}'", expected_return, actual_type));
|
||||
}
|
||||
|
||||
if return_constraint.is_some() {
|
||||
let symbolic = SymbolicEngine::new();
|
||||
if let Err(e) = symbolic.prove_constraint(expr, param_constraints) {
|
||||
return Err(format!("Proof Error: {}", e));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Stmt::ExprStmt(expr) => {
|
||||
self.evaluate_expr_type(expr)?;
|
||||
Ok(())
|
||||
}
|
||||
Stmt::LetBind(name, expr) => {
|
||||
let actual_type = self.evaluate_expr_type(expr)?;
|
||||
if !self.env.define_local(name.clone(), actual_type) {
|
||||
return Err(format!("Variable already defined in this scope: {}", name));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_expr_type(&mut self, expr: &Expr) -> Result<String, String> {
|
||||
match expr {
|
||||
Expr::Identifier(name) => {
|
||||
let ty = self.env.resolve_local(name).ok_or_else(|| format!("Undefined variable: {}", name))?;
|
||||
|
||||
if let Some(state) = self.env.get_ownership(name) {
|
||||
if state == OwnershipState::Moved {
|
||||
return Err(format!("Ownership Error: Use of moved variable '{}'", name));
|
||||
}
|
||||
// Mark as moved after successful consumption
|
||||
self.env.mark_moved(name)?;
|
||||
}
|
||||
|
||||
Ok(ty)
|
||||
}
|
||||
Expr::Literal(val) => {
|
||||
if val.contains('.') {
|
||||
Ok("f32".to_string())
|
||||
} else {
|
||||
Ok("i32".to_string())
|
||||
}
|
||||
}
|
||||
Expr::Binary(left, _op, right) => {
|
||||
let left_type = self.evaluate_expr_type(left)?;
|
||||
let right_type = self.evaluate_expr_type(right)?;
|
||||
|
||||
if left_type != right_type {
|
||||
return Err(format!("Type mismatch in binary operation: '{}' vs '{}'", left_type, right_type));
|
||||
}
|
||||
|
||||
Ok(left_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
113
aurac_typechecker/src/env.rs
Normal file
113
aurac_typechecker/src/env.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use std::collections::{HashSet, HashMap};
|
||||
use aurac_parser::ast::TypeExpr;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum OwnershipState {
|
||||
Uninitialized,
|
||||
Owned,
|
||||
Moved,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FunctionSignature {
|
||||
pub param_types: Vec<String>,
|
||||
pub return_type: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SymbolTable {
|
||||
pub defined_types: HashSet<String>,
|
||||
pub type_aliases: HashMap<String, TypeExpr>,
|
||||
pub functions: HashMap<String, FunctionSignature>,
|
||||
pub scopes: Vec<HashMap<String, (String, OwnershipState)>>,
|
||||
}
|
||||
|
||||
impl SymbolTable {
|
||||
pub fn new() -> Self {
|
||||
let mut defined_types = HashSet::new();
|
||||
let builtin_types = vec![
|
||||
"i8", "i16", "i32", "i64",
|
||||
"u8", "u16", "u32", "u64",
|
||||
"f32", "f64",
|
||||
"bool", "str"
|
||||
];
|
||||
|
||||
for ty in builtin_types {
|
||||
defined_types.insert(ty.to_string());
|
||||
}
|
||||
|
||||
Self {
|
||||
defined_types,
|
||||
type_aliases: HashMap::new(),
|
||||
functions: HashMap::new(),
|
||||
scopes: vec![HashMap::new()], // Global scope is scopes[0]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_type_defined(&self, name: &str) -> bool {
|
||||
self.defined_types.contains(name)
|
||||
}
|
||||
|
||||
pub fn register_type(&mut self, name: String) -> bool {
|
||||
self.defined_types.insert(name)
|
||||
}
|
||||
|
||||
pub fn register_function(&mut self, name: String, sig: FunctionSignature) -> bool {
|
||||
if self.functions.contains_key(&name) {
|
||||
false
|
||||
} else {
|
||||
self.functions.insert(name, sig);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enter_scope(&mut self) {
|
||||
self.scopes.push(HashMap::new());
|
||||
}
|
||||
|
||||
pub fn exit_scope(&mut self) {
|
||||
if self.scopes.len() > 1 {
|
||||
self.scopes.pop();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn define_local(&mut self, name: String, ty: String) -> bool {
|
||||
if let Some(scope) = self.scopes.last_mut() {
|
||||
if scope.contains_key(&name) {
|
||||
return false;
|
||||
}
|
||||
scope.insert(name, (ty, OwnershipState::Owned));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_local(&self, name: &str) -> Option<String> {
|
||||
for scope in self.scopes.iter().rev() {
|
||||
if let Some((ty, _)) = scope.get(name) {
|
||||
return Some(ty.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_ownership(&self, name: &str) -> Option<OwnershipState> {
|
||||
for scope in self.scopes.iter().rev() {
|
||||
if let Some((_, state)) = scope.get(name) {
|
||||
return Some(state.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn mark_moved(&mut self, name: &str) -> Result<(), String> {
|
||||
for scope in self.scopes.iter_mut().rev() {
|
||||
if let Some(entry) = scope.get_mut(name) {
|
||||
entry.1 = OwnershipState::Moved;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(format!("Variable not found: {}", name))
|
||||
}
|
||||
}
|
||||
285
aurac_typechecker/src/lib.rs
Normal file
285
aurac_typechecker/src/lib.rs
Normal file
@@ -0,0 +1,285 @@
|
||||
pub mod env;
|
||||
pub mod checker;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::checker::TypeChecker;
|
||||
use aurac_parser::ast::{Program, Decl, StructDecl, FieldDecl, TypeExpr};
|
||||
|
||||
#[test]
|
||||
fn test_valid_struct() {
|
||||
let program = Program {
|
||||
decls: vec![Decl::Struct(StructDecl {
|
||||
name: "Position".to_string(),
|
||||
fields: vec![
|
||||
FieldDecl { name: "x".to_string(), ty: TypeExpr::BaseType("f32".to_string()) },
|
||||
FieldDecl { name: "y".to_string(), ty: TypeExpr::BaseType("f32".to_string()) },
|
||||
],
|
||||
})],
|
||||
};
|
||||
|
||||
let mut checker = TypeChecker::new();
|
||||
let result = checker.check_program(&program);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_struct_duplicate_fields() {
|
||||
let program = Program {
|
||||
decls: vec![Decl::Struct(StructDecl {
|
||||
name: "DuplicateFieldStruct".to_string(),
|
||||
fields: vec![
|
||||
FieldDecl { name: "x".to_string(), ty: TypeExpr::BaseType("f32".to_string()) },
|
||||
FieldDecl { name: "x".to_string(), ty: TypeExpr::BaseType("f32".to_string()) },
|
||||
],
|
||||
})],
|
||||
};
|
||||
|
||||
let mut checker = TypeChecker::new();
|
||||
let result = checker.check_program(&program);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err("Duplicate field 'x' in struct 'DuplicateFieldStruct'".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_struct_unknown_type() {
|
||||
let program = Program {
|
||||
decls: vec![Decl::Struct(StructDecl {
|
||||
name: "UnknownTypeStruct".to_string(),
|
||||
fields: vec![
|
||||
FieldDecl { name: "val".to_string(), ty: TypeExpr::BaseType("float32_t".to_string()) },
|
||||
],
|
||||
})],
|
||||
};
|
||||
|
||||
let mut checker = TypeChecker::new();
|
||||
let result = checker.check_program(&program);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err("Unknown type 'float32_t' used in field 'val' of struct 'UnknownTypeStruct'".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_duplicate_struct_declaration() {
|
||||
let program = Program {
|
||||
decls: vec![
|
||||
Decl::Struct(StructDecl {
|
||||
name: "Data".to_string(),
|
||||
fields: vec![
|
||||
FieldDecl { name: "a".to_string(), ty: TypeExpr::BaseType("i32".to_string()) },
|
||||
],
|
||||
}),
|
||||
Decl::Struct(StructDecl {
|
||||
name: "Data".to_string(),
|
||||
fields: vec![
|
||||
FieldDecl { name: "b".to_string(), ty: TypeExpr::BaseType("f32".to_string()) },
|
||||
],
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
let mut checker = TypeChecker::new();
|
||||
let result = checker.check_program(&program);
|
||||
assert_eq!(result, Err("Duplicate type definition: Data".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_fn() {
|
||||
let program = Program {
|
||||
decls: vec![Decl::Fn(FnDecl {
|
||||
is_pure: true,
|
||||
is_gpu: false,
|
||||
name: "add".to_string(),
|
||||
params: vec![
|
||||
Param { name: "a".to_string(), ty: TypeExpr::BaseType("f32".to_string()) },
|
||||
Param { name: "b".to_string(), ty: TypeExpr::BaseType("f32".to_string()) },
|
||||
],
|
||||
return_type: TypeExpr::BaseType("f32".to_string()),
|
||||
body: Block {
|
||||
statements: vec![Stmt::Return(Expr::Binary(
|
||||
Box::new(Expr::Identifier("a".to_string())),
|
||||
aurac_parser::ast::BinaryOp::Add,
|
||||
Box::new(Expr::Identifier("b".to_string())),
|
||||
))],
|
||||
},
|
||||
})],
|
||||
};
|
||||
|
||||
let mut checker = TypeChecker::new();
|
||||
let result = checker.check_program(&program);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_return_type() {
|
||||
let program = Program {
|
||||
decls: vec![Decl::Fn(FnDecl {
|
||||
is_pure: true,
|
||||
is_gpu: false,
|
||||
name: "add".to_string(),
|
||||
params: vec![
|
||||
Param { name: "a".to_string(), ty: TypeExpr::BaseType("f32".to_string()) },
|
||||
Param { name: "b".to_string(), ty: TypeExpr::BaseType("f32".to_string()) },
|
||||
],
|
||||
return_type: TypeExpr::BaseType("i32".to_string()), // mismatch here
|
||||
body: Block {
|
||||
statements: vec![Stmt::Return(Expr::Binary(
|
||||
Box::new(Expr::Identifier("a".to_string())),
|
||||
aurac_parser::ast::BinaryOp::Add,
|
||||
Box::new(Expr::Identifier("b".to_string())),
|
||||
))],
|
||||
},
|
||||
})],
|
||||
};
|
||||
|
||||
let mut checker = TypeChecker::new();
|
||||
let result = checker.check_program(&program);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err("Type mismatch: expected return type 'i32', but found 'f32'".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_undefined_variable() {
|
||||
let program = Program {
|
||||
decls: vec![Decl::Fn(FnDecl {
|
||||
is_pure: true,
|
||||
is_gpu: false,
|
||||
name: "add".to_string(),
|
||||
params: vec![
|
||||
Param { name: "a".to_string(), ty: TypeExpr::BaseType("f32".to_string()) },
|
||||
],
|
||||
return_type: TypeExpr::BaseType("f32".to_string()),
|
||||
body: Block {
|
||||
statements: vec![Stmt::Return(Expr::Binary(
|
||||
Box::new(Expr::Identifier("a".to_string())),
|
||||
aurac_parser::ast::BinaryOp::Add,
|
||||
Box::new(Expr::Identifier("c".to_string())), // 'c' is undefined
|
||||
))],
|
||||
},
|
||||
})],
|
||||
};
|
||||
|
||||
let mut checker = TypeChecker::new();
|
||||
let result = checker.check_program(&program);
|
||||
assert_eq!(result, Err("Undefined variable: c".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_refinement_proof_success() {
|
||||
let program = Program {
|
||||
decls: vec![
|
||||
Decl::TypeAlias("Positive".to_string(), TypeExpr::Refined(
|
||||
Box::new(TypeExpr::BaseType("f32".to_string())),
|
||||
"v".to_string(),
|
||||
Box::new(Expr::Binary(
|
||||
Box::new(Expr::Identifier("v".to_string())),
|
||||
aurac_parser::ast::BinaryOp::Gt,
|
||||
Box::new(Expr::Literal("0.0".to_string())),
|
||||
))
|
||||
)),
|
||||
Decl::Fn(FnDecl {
|
||||
is_pure: true,
|
||||
name: "add".to_string(),
|
||||
params: vec![
|
||||
Param { name: "a".to_string(), ty: TypeExpr::BaseType("Positive".to_string()) },
|
||||
Param { name: "b".to_string(), ty: TypeExpr::BaseType("Positive".to_string()) },
|
||||
],
|
||||
return_type: TypeExpr::BaseType("Positive".to_string()),
|
||||
body: Block {
|
||||
statements: vec![Stmt::Return(Expr::Binary(
|
||||
Box::new(Expr::Identifier("a".to_string())),
|
||||
aurac_parser::ast::BinaryOp::Add,
|
||||
Box::new(Expr::Identifier("b".to_string())),
|
||||
))],
|
||||
},
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
let mut checker = TypeChecker::new();
|
||||
let result = checker.check_program(&program);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_refinement_proof_failure() {
|
||||
let program = Program {
|
||||
decls: vec![
|
||||
Decl::TypeAlias("Positive".to_string(), TypeExpr::Refined(
|
||||
Box::new(TypeExpr::BaseType("f32".to_string())),
|
||||
"v".to_string(),
|
||||
Box::new(Expr::Binary(
|
||||
Box::new(Expr::Identifier("v".to_string())),
|
||||
aurac_parser::ast::BinaryOp::Gt,
|
||||
Box::new(Expr::Literal("0.0".to_string())),
|
||||
))
|
||||
)),
|
||||
Decl::Fn(FnDecl {
|
||||
is_pure: true,
|
||||
name: "sub".to_string(),
|
||||
params: vec![
|
||||
Param { name: "a".to_string(), ty: TypeExpr::BaseType("Positive".to_string()) },
|
||||
Param { name: "b".to_string(), ty: TypeExpr::BaseType("Positive".to_string()) },
|
||||
],
|
||||
return_type: TypeExpr::BaseType("Positive".to_string()),
|
||||
body: Block {
|
||||
statements: vec![Stmt::Return(Expr::Binary(
|
||||
Box::new(Expr::Identifier("a".to_string())),
|
||||
aurac_parser::ast::BinaryOp::Sub,
|
||||
Box::new(Expr::Identifier("b".to_string())),
|
||||
))],
|
||||
},
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
let mut checker = TypeChecker::new();
|
||||
let result = checker.check_program(&program);
|
||||
assert_eq!(result, Err("Proof Error: Cannot mathematically prove constraint: result might be <= 0.0".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ownership_use_after_move() {
|
||||
let program = Program {
|
||||
decls: vec![
|
||||
Decl::Fn(FnDecl {
|
||||
is_pure: true,
|
||||
name: "process".to_string(),
|
||||
params: vec![
|
||||
Param { name: "d".to_string(), ty: TypeExpr::BaseType("f32".to_string()) },
|
||||
],
|
||||
return_type: TypeExpr::BaseType("f32".to_string()),
|
||||
body: Block {
|
||||
statements: vec![Stmt::Return(Expr::Identifier("d".to_string()))],
|
||||
},
|
||||
}),
|
||||
Decl::Fn(FnDecl {
|
||||
is_pure: true,
|
||||
name: "main".to_string(),
|
||||
params: vec![],
|
||||
return_type: TypeExpr::BaseType("f32".to_string()),
|
||||
body: Block {
|
||||
statements: vec![
|
||||
// let a = 10.0
|
||||
Stmt::LetBind("a".to_string(), Expr::Literal("10.0".to_string())),
|
||||
// let b = process(a) => Since there's no native function calls compiled yet, we simulate passing 'a' into a mathematical operation or tracking sequence
|
||||
Stmt::LetBind("b".to_string(), Expr::Identifier("a".to_string())),
|
||||
// let c = a => Use-after-move!
|
||||
Stmt::LetBind("c".to_string(), Expr::Identifier("a".to_string())),
|
||||
Stmt::Return(Expr::Identifier("b".to_string())),
|
||||
],
|
||||
},
|
||||
}),
|
||||
]
|
||||
};
|
||||
|
||||
let mut checker = TypeChecker::new();
|
||||
let result = checker.check_program(&program);
|
||||
assert_eq!(result, Err("Ownership Error: Use of moved variable 'a'".to_string()));
|
||||
}
|
||||
}
|
||||
45
aurac_typechecker/src/symbolic.rs
Normal file
45
aurac_typechecker/src/symbolic.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use aurac_parser::ast::{Expr, BinaryOp};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct SymbolicEngine;
|
||||
|
||||
impl SymbolicEngine {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
pub fn prove_constraint(&self, expr: &Expr, constraints: &HashMap<String, Expr>) -> Result<(), String> {
|
||||
if let Expr::Binary(left, op, right) = expr {
|
||||
match op {
|
||||
BinaryOp::Add => {
|
||||
// Two positive values summed remain strictly positive
|
||||
if self.is_positive(left, constraints) && self.is_positive(right, constraints) {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err("Cannot mathematically prove constraint: result might be <= 0.0".to_string());
|
||||
}
|
||||
}
|
||||
} else if self.is_positive(expr, constraints) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err("Cannot mathematically prove constraint: result might be <= 0.0".to_string())
|
||||
}
|
||||
|
||||
fn is_positive(&self, expr: &Expr, constraints: &HashMap<String, Expr>) -> bool {
|
||||
if let Expr::Identifier(id) = expr {
|
||||
if let Some(constraint) = constraints.get(id) {
|
||||
if let Expr::Binary(_, BinaryOp::Gt, c_right) = constraint {
|
||||
if let Expr::Literal(lit) = &**c_right {
|
||||
if lit == "0.0" {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
13
crash_math.aura
Normal file
13
crash_math.aura
Normal file
@@ -0,0 +1,13 @@
|
||||
type PositiveTime = f32{t | t > 0.0}
|
||||
|
||||
pure fn simulate(dt: PositiveTime) -> f32:
|
||||
// CRASH: We are subtracting a value from a constrained positive type,
|
||||
// which could result in a negative number, violating the return type if we expected a PositiveTime.
|
||||
// For this test, let's just pass an invalid type.
|
||||
return dt
|
||||
|
||||
pure fn main() -> f32:
|
||||
// CRASH: Trying to pass a negative literal to a PositiveTime constraint!
|
||||
let invalid_time = -5.0
|
||||
let result = simulate(invalid_time)
|
||||
return result
|
||||
5
crash_memory.aura
Normal file
5
crash_memory.aura
Normal file
@@ -0,0 +1,5 @@
|
||||
pure fn calculate_speed(distance: f32, time: f32) -> f32:
|
||||
let temp_dist = distance
|
||||
// CRASH: 'distance' was just moved to 'temp_dist'. We cannot use it again!
|
||||
let speed = distance / time
|
||||
return speed
|
||||
12
main.c
Normal file
12
main.c
Normal file
@@ -0,0 +1,12 @@
|
||||
#include <stdio.h>
|
||||
|
||||
extern float calculate_new_position(float initial_x, float v, float dt);
|
||||
|
||||
int main() {
|
||||
float initial_x = 100.0f;
|
||||
float v = 50.0f;
|
||||
float dt = 2.0f;
|
||||
float new_x = calculate_new_position(initial_x, v, dt);
|
||||
printf("Aura Physics Simulation Result: new_x = %f\n", new_x);
|
||||
return 0;
|
||||
}
|
||||
6
physics.aura
Normal file
6
physics.aura
Normal file
@@ -0,0 +1,6 @@
|
||||
type PositiveTime = f32{t | t > 0.0}
|
||||
|
||||
pure fn calculate_new_position(initial_x: f32, v: f32, dt: PositiveTime) -> f32:
|
||||
let displacement = v * dt
|
||||
let new_x = initial_x + displacement
|
||||
return new_x
|
||||
Reference in New Issue
Block a user