diff --git a/examples/globals_basic.kit b/examples/globals_basic.kit new file mode 100644 index 0000000..ab2a344 --- /dev/null +++ b/examples/globals_basic.kit @@ -0,0 +1,24 @@ +include "stdio.h"; + +var globalInt: Int = 42; +const globalConstInt: Int = 100; +var globalFloat: Float = 3.14; +const globalConstFloat: Float = 2.718; +var globalBool: Bool = true; +const globalConstBool: Bool = false; + +function main() { + printf("globalInt: %d\n", globalInt); + printf("globalConstInt: %d\n", globalConstInt); + printf("globalFloat: %f\n", globalFloat); + printf("globalConstFloat: %f\n", globalConstFloat); + printf("globalBool: %d\n", globalBool); + printf("globalConstBool: %d\n", globalConstBool); + + globalInt = 999; + globalFloat = 1.5; + globalBool = false; + printf("modified globalInt: %d\n", globalInt); + printf("modified globalFloat: %f\n", globalFloat); + printf("modified globalBool: %d\n", globalBool); +} diff --git a/examples/globals_basic.kit.expected b/examples/globals_basic.kit.expected new file mode 100644 index 0000000..6fe43f0 --- /dev/null +++ b/examples/globals_basic.kit.expected @@ -0,0 +1,9 @@ +globalInt: 42 +globalConstInt: 100 +globalFloat: 3.140000 +globalConstFloat: 2.718000 +globalBool: 1 +globalConstBool: 0 +modified globalInt: 999 +modified globalFloat: 1.500000 +modified globalBool: 0 diff --git a/examples/globals_comprehensive.kit b/examples/globals_comprehensive.kit new file mode 100644 index 0000000..3664ec0 --- /dev/null +++ b/examples/globals_comprehensive.kit @@ -0,0 +1,48 @@ +include "stdio.h"; + +var globalInt: Int = 42; +const globalConstInt: Int = 100; + +var globalFloat: Float = 3.14; +const globalConstFloat: Float = 2.718; + +var globalString = "hello"; +const globalConstString = "world"; + +var globalBool: Bool = true; +const globalConstBool: Bool = false; + +// Test global referencing another global +var globalRef: Int = 52; +const globalConstRef: Int = globalConstInt + 50; + +function main() { + printf("--- Basic Types ---\n"); + printf("globalInt: %d\n", globalInt); + printf("globalConstInt: %d\n", globalConstInt); + + printf("globalFloat: %f\n", globalFloat); + printf("globalConstFloat: %f\n", globalConstFloat); + + printf("globalString: %s\n", globalString); + printf("globalConstString: %s\n", globalConstString); + + printf("globalBool: %d\n", globalBool); + printf("globalConstBool: %d\n", globalConstBool); + + printf("--- References ---\n"); + printf("globalRef: %d\n", globalRef); + printf("globalConstRef: %d\n", globalConstRef); + + printf("--- Modification ---\n"); + + globalInt = 999; + globalFloat = 1.5; + globalString = "modified"; + globalBool = false; + + printf("modified globalInt: %d\n", globalInt); + printf("modified globalFloat: %f\n", globalFloat); + printf("modified globalString: %s\n", globalString); + printf("modified globalBool: %d\n", globalBool); +} diff --git a/examples/globals_comprehensive.kit.expected b/examples/globals_comprehensive.kit.expected new file mode 100644 index 0000000..036e894 --- /dev/null +++ b/examples/globals_comprehensive.kit.expected @@ -0,0 +1,17 @@ +--- Basic Types --- +globalInt: 42 +globalConstInt: 100 +globalFloat: 3.140000 +globalConstFloat: 2.718000 +globalString: hello +globalConstString: world +globalBool: 1 +globalConstBool: 0 +--- References --- +globalRef: 52 +globalConstRef: 150 +--- Modification --- +modified globalInt: 999 +modified globalFloat: 1.500000 +modified globalString: modified +modified globalBool: 0 diff --git a/examples/globals_test.kit b/examples/globals_test.kit new file mode 100644 index 0000000..ea6ecb8 --- /dev/null +++ b/examples/globals_test.kit @@ -0,0 +1,17 @@ +include "stdio.h"; + +var globalVar: Int = 42; +const globalConst: Int = 100; +var globalWithInference = 123; +const constWithInference = 456; + +function main() { + printf("globalVar: %d\n", globalVar); + printf("globalConst: %d\n", globalConst); + printf("globalWithInference: %d\n", globalWithInference); + printf("constWithInference: %d\n", constWithInference); + + // Test modifying global variable + globalVar = 999; + printf("modified globalVar: %d\n", globalVar); +} diff --git a/examples/globals_test.kit.expected b/examples/globals_test.kit.expected new file mode 100644 index 0000000..5eab97e --- /dev/null +++ b/examples/globals_test.kit.expected @@ -0,0 +1,5 @@ +globalVar: 42 +globalConst: 100 +globalWithInference: 123 +constWithInference: 456 +modified globalVar: 999 diff --git a/kitc/tests/examples.rs b/kitc/tests/examples.rs index d2690f9..bc670d7 100644 --- a/kitc/tests/examples.rs +++ b/kitc/tests/examples.rs @@ -197,6 +197,21 @@ fn test_enum_defaults() -> Result<(), Box> { run_example_test("enum_defaults", None) } +#[test] +fn test_globals_basic() -> Result<(), Box> { + run_example_test("globals_basic", None) +} + +#[test] +fn test_globals_comprehensive() -> Result<(), Box> { + run_example_test("globals_comprehensive", None) +} + +#[test] +fn test_globals_test() -> Result<(), Box> { + run_example_test("globals_test", None) +} + #[test] fn test_nested_comments() -> Result<(), Box> { let workspace_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) diff --git a/kitlang/src/codegen/ast.rs b/kitlang/src/codegen/ast.rs index 34c8332..6829b48 100644 --- a/kitlang/src/codegen/ast.rs +++ b/kitlang/src/codegen/ast.rs @@ -191,7 +191,7 @@ pub enum Expr { }, } -/// Represents literal values in Kit. +/// Represents a literal value in Kit. #[derive(Clone, Debug, PartialEq)] pub enum Literal { /// Signed integer literal. @@ -206,6 +206,21 @@ pub enum Literal { Null, } +/// Represents a global variable or constant declaration. +#[derive(Clone, Debug, PartialEq)] +pub struct GlobalDecl { + /// Variable name. + pub name: String, + /// Type annotation (`None` for type inference). + pub annotation: Option, + /// Inferred variable type ID. + pub inferred: TypeId, + /// Initializer expression (`None` for uninitialized). + pub init: Option, + /// Whether this is a const declaration. + pub is_const: bool, +} + impl Literal { /// Converts the literal to its C representation string. #[must_use] @@ -247,6 +262,8 @@ pub struct Program { pub includes: Vec, /// Kit module imports (not directly used in C generation). pub imports: HashSet, + /// Top-level global variable and constant declarations. + pub globals: Vec, /// Top-level function definitions. pub functions: Vec, /// Struct type definitions. diff --git a/kitlang/src/codegen/frontend.rs b/kitlang/src/codegen/frontend.rs index 49248f6..3ddfe40 100644 --- a/kitlang/src/codegen/frontend.rs +++ b/kitlang/src/codegen/frontend.rs @@ -7,7 +7,7 @@ use std::fmt::Write; use std::path::{Path, PathBuf}; use std::process::Command; -use crate::codegen::ast::{Block, Expr, Function, Include, Program, Stmt}; +use crate::codegen::ast::{Block, Expr, Function, GlobalDecl, Include, Program, Stmt}; use crate::codegen::compiler::{CompilerMeta, CompilerOptions, Toolchain}; use crate::codegen::inference::TypeInferencer; use crate::codegen::parser::Parser as CodeParser; @@ -39,6 +39,7 @@ impl Compiler { fn parse(&mut self) -> CompileResult { let mut includes = Vec::new(); + let mut globals = Vec::new(); let mut functions = Vec::new(); let mut structs = Vec::new(); let mut enums = Vec::new(); @@ -65,6 +66,10 @@ impl Compiler { includes.push(self.parser.parse_include(pair)); } + Rule::var_decl => { + globals.push(self.parser.parse_global_var_decl(pair)?); + } + Rule::function_decl => { functions.push(self.parser.parse_function(pair)?); } @@ -95,6 +100,7 @@ impl Compiler { Ok(Program { includes, imports: HashSet::new(), + globals, functions, structs, enums, @@ -135,16 +141,36 @@ impl Compiler { } }; - // Emit struct declarations first + // Scan all types to gather required headers BEFORE emitting code that uses them + // Scan struct field types for struct_def in &prog.structs { - out.push_str(&self.generate_struct_declaration(struct_def, &prog.structs)); - out.push('\n'); + for field in &struct_def.fields { + if let Ok(ty) = self.inferencer.store.resolve(field.ty) { + collect_from_type(&ty); + } else if let Some(ann) = &field.annotation { + collect_from_type(ann); + } + } } - // Emit enum declarations + // Scan enum variant argument types for enum_def in &prog.enums { - out.push_str(&self.generate_enum_declaration(enum_def)); - out.push('\n'); + for variant in &enum_def.variants { + for arg in &variant.args { + if let Ok(ty) = self.inferencer.store.resolve(arg.ty) { + collect_from_type(&ty); + } else if let Some(ann) = &arg.annotation { + collect_from_type(ann); + } + } + } + } + + // Scan global variable types + for global in &prog.globals { + if let Ok(ty) = self.inferencer.store.resolve(global.inferred) { + collect_from_type(&ty); + } } // scan every function signature & body for types to gather their headers/typedefs @@ -188,6 +214,24 @@ impl Compiler { out.push('\n'); } + // Emit struct declarations + for struct_def in &prog.structs { + out.push_str(&self.generate_struct_declaration(struct_def, &prog.structs)); + out.push('\n'); + } + + // Emit enum declarations + for enum_def in &prog.enums { + out.push_str(&self.generate_enum_declaration(enum_def)); + out.push('\n'); + } + + // Emit global variable declarations + for global in &prog.globals { + out.push_str(&self.transpile_global(global)); + out.push('\n'); + } + // emit functions as before... for func in &prog.functions { out.push_str(&self.transpile_function(func)); @@ -196,6 +240,35 @@ impl Compiler { out } + fn transpile_global(&self, global: &GlobalDecl) -> String { + let ty = self.inferencer.store.resolve(global.inferred).map_or_else( + |_| "int".to_string(), + |t| { + if let Type::Named(name) = &t { + if self.inferencer.is_struct_type(name) { + format!("struct {}", name) + } else { + t.to_c_repr().name + } + } else { + t.to_c_repr().name + } + }, + ); + + let const_prefix = if global.is_const { "const " } else { "" }; + + match &global.init { + Some(expr) => { + let init_str = self.transpile_expr(expr); + format!("{const_prefix}{ty} {} = {init_str};", global.name) + } + None => { + format!("{const_prefix}{ty} {};", global.name) + } + } + } + fn generate_struct_declaration( &self, struct_def: &StructDefinition, diff --git a/kitlang/src/codegen/inference.rs b/kitlang/src/codegen/inference.rs index 6899364..7b8b340 100644 --- a/kitlang/src/codegen/inference.rs +++ b/kitlang/src/codegen/inference.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use super::Field; -use super::ast::{Block, Expr, Function, Literal, Program, Stmt}; +use super::ast::{Block, Expr, Function, GlobalDecl, Literal, Program, Stmt}; use super::symbols::{EnumVariantInfo, SymbolTable}; use super::type_ast::{EnumDefinition, FieldInit, StructDefinition}; use super::types::{BinaryOperator, Type, TypeId, TypeStore, UnaryOperator}; @@ -44,12 +44,44 @@ impl TypeInferencer { self.register_enum_types(&prog.enums)?; self.register_struct_types(&prog.structs)?; + // Infer global variable types first (before functions) + self.infer_globals(&mut prog.globals)?; + for func in &mut prog.functions { self.infer_function(func)?; } Ok(()) } + /// Infer types for global variable declarations + fn infer_globals(&mut self, globals: &mut [GlobalDecl]) -> CompileResult<()> { + for global in globals { + if let Some(init_expr) = &mut global.init { + let init_ty = self.infer_expr(init_expr)?; + + global.inferred = if let Some(ann) = &global.annotation { + let ann_ty = self.store.new_known(ann.clone()); + self.unify(ann_ty, init_ty)?; + ann_ty + } else { + init_ty + }; + + self.symbols.define_global(&global.name, global.inferred); + } else if let Some(ann) = &global.annotation { + // Declaration without initializer -> just use annotation + global.inferred = self.store.new_known(ann.clone()); + self.symbols.define_global(&global.name, global.inferred); + } else { + return Err(CompilationError::TypeError(format!( + "Global variable '{}' declared without type annotation or initializer", + global.name + ))); + } + } + Ok(()) + } + /// Register enum types in the type store and symbol table fn register_enum_types(&mut self, enums: &[EnumDefinition]) -> CompileResult<()> { for enum_def in enums { @@ -264,7 +296,11 @@ impl TypeInferencer { fn infer_expr(&mut self, expr: &mut Expr) -> Result { let ty = match expr { Expr::Identifier(name, ty_id) => { - if let Some(var_ty) = self.symbols.lookup_var(name) { + // First check if it's a global variable + if let Some(global_ty) = self.symbols.lookup_global(name) { + *ty_id = global_ty; + global_ty + } else if let Some(var_ty) = self.symbols.lookup_var(name) { *ty_id = var_ty; var_ty } else { diff --git a/kitlang/src/codegen/parser.rs b/kitlang/src/codegen/parser.rs index f725d65..907a4f3 100644 --- a/kitlang/src/codegen/parser.rs +++ b/kitlang/src/codegen/parser.rs @@ -4,7 +4,7 @@ use crate::codegen::types::{BinaryOperator, UnaryOperator}; use crate::error::CompilationError; use crate::{Rule, parse_error}; -use super::ast::{Block, Expr, Function, Include, Literal, Param, Stmt}; +use super::ast::{Block, Expr, Function, GlobalDecl, Include, Literal, Param, Stmt}; use super::type_ast::{EnumDefinition, EnumVariant, Field, FieldInit, StructDefinition}; use super::types::{AssignmentOperator, Type, TypeId}; use crate::error::CompileResult; @@ -372,6 +372,32 @@ impl Parser { }) } + pub fn parse_global_var_decl(&self, pair: Pair) -> CompileResult { + // Parse a global variable or constant declaration at module level + let name = Self::extract_first_identifier(pair.clone()) + .ok_or(parse_error!("global var_decl missing identifier"))?; + + let is_const = Self::is_const_var_decl(pair.clone()); + + // Parse type annotation if present + let annotation = Self::extract_type_annotation(pair.clone()) + .map(|type_pair| self.parse_type(type_pair)) + .transpose()?; + + // Parse initializer expression if present + let init = Self::extract_init_expr(pair.clone()) + .map(|expr_pair| self.parse_expr(expr_pair)) + .transpose()?; + + Ok(GlobalDecl { + name, + annotation, + inferred: TypeId::default(), + init, + is_const, + }) + } + /// Extract initializer expression from a var_decl pair fn extract_init_expr(pair: Pair<'_, Rule>) -> Option> { pair.into_inner().find(|p| p.as_rule() == Rule::expr) diff --git a/kitlang/src/codegen/symbols.rs b/kitlang/src/codegen/symbols.rs index 383304d..0305fdc 100644 --- a/kitlang/src/codegen/symbols.rs +++ b/kitlang/src/codegen/symbols.rs @@ -16,7 +16,10 @@ pub struct EnumVariantInfo { /// Currently uses a flat scope (no nesting). Variables and functions are tracked /// by their names and their `TypeId`s. pub struct SymbolTable { - /// Maps variable names to their inferred `TypeId`s. + /// Maps global variable names to their inferred `TypeId`s. + globals: HashMap, + + /// Maps local variable names to their inferred `TypeId`s. vars: HashMap, /// Maps function names to their signatures (parameter types, return type). @@ -41,6 +44,7 @@ impl Default for SymbolTable { impl SymbolTable { pub fn new() -> Self { Self { + globals: HashMap::new(), vars: HashMap::new(), functions: HashMap::new(), structs: HashMap::new(), @@ -49,6 +53,16 @@ impl SymbolTable { } } + /// Define a global variable in the symbol table. + pub fn define_global(&mut self, name: &str, ty: TypeId) { + self.globals.insert(name.to_string(), ty); + } + + /// Look up a global variable's type. + pub fn lookup_global(&self, name: &str) -> Option { + self.globals.get(name).copied() + } + /// Define a variable in the current scope. pub fn define_var(&mut self, name: &str, ty: TypeId) { self.vars.insert(name.to_string(), ty); diff --git a/kitlang/src/grammar/kit.pest b/kitlang/src/grammar/kit.pest index 7dcaeaf..1a568e4 100644 --- a/kitlang/src/grammar/kit.pest +++ b/kitlang/src/grammar/kit.pest @@ -7,7 +7,7 @@ COMMENT_LINE = _{ "//" ~ (!NEWLINE ~ ANY)* ~ NEWLINE } var_kw = { "var" } const_kw = { "const" } -program = _{ SOI ~ (import_stmt | include_stmt | function_decl | type_def | trait_def | trait_impl | rule_set | using_stmt)* ~ EOI } +program = _{ SOI ~ (import_stmt | include_stmt | function_decl | var_decl | type_def | trait_def | trait_impl | rule_set | using_stmt)* ~ EOI } import_stmt = { "import" ~ path ~ ("." ~ ("*" | "**"))? ~ ";" } include_stmt = { "include" ~ string ~ ("=>" ~ string)? ~ ";" }