From 0e0f2bd3523c33e7cd1ec8987b8f76f90b593381 Mon Sep 17 00:00:00 2001 From: codex Date: Thu, 19 Mar 2026 18:30:51 +0530 Subject: [PATCH 1/8] chore: remove rey-vscode submodule --- syntax.md | 1 - 1 file changed, 1 deletion(-) diff --git a/syntax.md b/syntax.md index e497e85..897ee65 100644 --- a/syntax.md +++ b/syntax.md @@ -563,7 +563,6 @@ The following keywords are reserved for future features: - `catch` Note: `enum` and `match` were previously reserved and are now fully implemented. - --- ## Error Diagnostics From fd379cb9fe4f4eee99d23b1fef42c4a2dacff42d Mon Sep 17 00:00:00 2001 From: codex Date: Mon, 23 Mar 2026 01:30:04 +0530 Subject: [PATCH 2/8] feat(parser): add export pub visibility and import syntax ast --- compiler/v1/src/ast/mod.rs | 5 +- compiler/v1/src/ast/stmt.rs | 20 +++++ compiler/v1/src/interpreter/executor.rs | 7 +- compiler/v1/src/lexer/lexer.rs | 3 + compiler/v1/src/lexer/token.rs | 3 + compiler/v1/src/parser/parser.rs | 115 +++++++++++++++++++++++- compiler/v1/src/typecheck.rs | 3 + 7 files changed, 151 insertions(+), 5 deletions(-) diff --git a/compiler/v1/src/ast/mod.rs b/compiler/v1/src/ast/mod.rs index 6d93a34..b315138 100644 --- a/compiler/v1/src/ast/mod.rs +++ b/compiler/v1/src/ast/mod.rs @@ -5,5 +5,8 @@ pub mod ty; pub use expr::Expr; pub use literal::Literal; -pub use stmt::{FieldDecl, ForIterator, MatchArm, MethodDecl, Parameter, Pattern, Stmt}; +pub use stmt::{ + FieldDecl, ForIterator, FunctionVisibility, ImportKind, MatchArm, MethodDecl, Parameter, + Pattern, Stmt, +}; pub use ty::Type; diff --git a/compiler/v1/src/ast/stmt.rs b/compiler/v1/src/ast/stmt.rs index 7b87986..2206ef5 100644 --- a/compiler/v1/src/ast/stmt.rs +++ b/compiler/v1/src/ast/stmt.rs @@ -1,4 +1,5 @@ use super::{Expr, Literal, Type}; +use crate::lexer::span::Span; #[derive(Debug, Clone, PartialEq)] pub struct Parameter { @@ -25,6 +26,20 @@ pub struct MethodDecl { pub is_static: bool, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FunctionVisibility { + Private, + Pub, + ExportPub, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ImportKind { + FileSymbols { module: String, symbols: Vec }, + ModuleNamespace { module: String }, + ModuleItems { module: String, items: Vec }, +} + #[derive(Debug, Clone, PartialEq)] pub enum Stmt { VarDecl { @@ -35,6 +50,7 @@ pub enum Stmt { }, FuncDecl { name: String, + visibility: FunctionVisibility, params: Vec, return_ty: Option, body: Vec, @@ -44,6 +60,10 @@ pub enum Stmt { fields: Vec, methods: Vec, }, + Import { + kind: ImportKind, + span: Span, + }, If { condition: Expr, then_branch: Vec, diff --git a/compiler/v1/src/interpreter/executor.rs b/compiler/v1/src/interpreter/executor.rs index 0d45ebe..44c4b98 100644 --- a/compiler/v1/src/interpreter/executor.rs +++ b/compiler/v1/src/interpreter/executor.rs @@ -33,7 +33,11 @@ impl Executor { Ok(ControlFlow::normal(value)) } Stmt::FuncDecl { - name, params, body, .. + name, + visibility: _, + params, + body, + .. } => { let function = Function::new( name.clone(), @@ -208,6 +212,7 @@ impl Executor { Err("No matching arm in match expression".to_string()) } + Stmt::Import { .. } => Ok(ControlFlow::normal(Value::Null)), } } diff --git a/compiler/v1/src/lexer/lexer.rs b/compiler/v1/src/lexer/lexer.rs index 43bb4a3..73ebfed 100644 --- a/compiler/v1/src/lexer/lexer.rs +++ b/compiler/v1/src/lexer/lexer.rs @@ -373,6 +373,9 @@ impl<'a> Lexer<'a> { "var" => TokenKind::Var, "const" => TokenKind::Const, "func" => TokenKind::Func, + "import" => TokenKind::Import, + "export" => TokenKind::Export, + "pub" => TokenKind::Pub, "return" => TokenKind::Return, "if" => TokenKind::If, "else" => TokenKind::Else, diff --git a/compiler/v1/src/lexer/token.rs b/compiler/v1/src/lexer/token.rs index 14eddb6..8fd87cf 100644 --- a/compiler/v1/src/lexer/token.rs +++ b/compiler/v1/src/lexer/token.rs @@ -47,6 +47,9 @@ pub enum TokenKind { Var, Const, Func, + Import, + Export, + Pub, Return, If, Else, diff --git a/compiler/v1/src/parser/parser.rs b/compiler/v1/src/parser/parser.rs index 71cc3bc..447030d 100644 --- a/compiler/v1/src/parser/parser.rs +++ b/compiler/v1/src/parser/parser.rs @@ -7,7 +7,10 @@ #![allow(non_snake_case)] -use crate::ast::{Expr, FieldDecl, Literal, MatchArm, MethodDecl, Parameter, Pattern, Stmt, Type}; +use crate::ast::{ + Expr, FieldDecl, FunctionVisibility, ImportKind, Literal, MatchArm, MethodDecl, Parameter, + Pattern, Stmt, Type, +}; use crate::lexer::{span::Span, Token, TokenKind}; use crate::parser::error::ParserError; @@ -45,8 +48,20 @@ impl Parser { Ok(Some(self.parseVarDeclaration(false)?)) } else if self.matchToken(&TokenKind::Const) { Ok(Some(self.parseVarDeclaration(true)?)) + } else if self.matchToken(&TokenKind::Import) { + Ok(Some(self.parseImportStatement()?)) + } else if self.matchToken(&TokenKind::Export) { + self.consume(&TokenKind::Pub, "Expected 'pub' after 'export'.")?; + self.consume( + &TokenKind::Func, + "Expected 'func' after 'export pub' modifier.", + )?; + Ok(Some(self.parseFuncDeclaration(FunctionVisibility::ExportPub)?)) + } else if self.matchToken(&TokenKind::Pub) { + self.consume(&TokenKind::Func, "Expected 'func' after 'pub' modifier.")?; + Ok(Some(self.parseFuncDeclaration(FunctionVisibility::Pub)?)) } else if self.matchToken(&TokenKind::Func) { - Ok(Some(self.parseFuncDeclaration()?)) + Ok(Some(self.parseFuncDeclaration(FunctionVisibility::Private)?)) } else if self.matchToken(&TokenKind::Struct) { Ok(Some(self.parseStructDeclaration()?)) } else if self.matchToken(&TokenKind::Enum) { @@ -95,7 +110,7 @@ impl Parser { }) } - fn parseFuncDeclaration(&mut self) -> Result { + fn parseFuncDeclaration(&mut self, visibility: FunctionVisibility) -> Result { let name = match &self.peek().kind { TokenKind::Identifier(name) => name.clone(), _ => return Err(self.error("Expected function name.")), @@ -164,6 +179,7 @@ impl Parser { Ok(Stmt::FuncDecl { name, + visibility, params, return_ty, body, @@ -290,6 +306,84 @@ impl Parser { }) } + fn parseImportStatement(&mut self) -> Result { + let import_span = self.previous().span; + let module = match &self.peek().kind { + TokenKind::Identifier(name) => name.clone(), + _ => return Err(self.error("Expected module or file name after 'import'.")), + }; + self.advance(); + + let kind = if self.matchToken(&TokenKind::Dot) { + let symbols = if self.matchToken(&TokenKind::LeftBrace) { + let mut values = Vec::new(); + loop { + let name = match &self.peek().kind { + TokenKind::Identifier(name) => name.clone(), + _ => { + return Err( + self.error("Expected identifier in grouped file import list.") + ) + } + }; + self.advance(); + values.push(name); + if !self.matchToken(&TokenKind::Comma) { + break; + } + } + self.consume( + &TokenKind::RightBrace, + "Expected '}' after grouped file import list.", + )?; + values + } else { + let symbol = match &self.peek().kind { + TokenKind::Identifier(name) => name.clone(), + _ => return Err(self.error("Expected symbol name after file import '.'.")), + }; + self.advance(); + vec![symbol] + }; + ImportKind::FileSymbols { module, symbols } + } else if self.matchDoubleColon() { + let items = if self.matchToken(&TokenKind::LeftBrace) { + let mut values = Vec::new(); + loop { + let name = match &self.peek().kind { + TokenKind::Identifier(name) => name.clone(), + _ => return Err(self.error("Expected identifier in grouped module import list.")), + }; + self.advance(); + values.push(name); + if !self.matchToken(&TokenKind::Comma) { + break; + } + } + self.consume( + &TokenKind::RightBrace, + "Expected '}' after grouped module import list.", + )?; + values + } else { + let item = match &self.peek().kind { + TokenKind::Identifier(name) => name.clone(), + _ => return Err(self.error("Expected file name after '::' in module import.")), + }; + self.advance(); + vec![item] + }; + ImportKind::ModuleItems { module, items } + } else { + ImportKind::ModuleNamespace { module } + }; + self.consume(&TokenKind::Semicolon, "Expected ';' after import statement.")?; + Ok(Stmt::Import { + kind, + span: import_span, + }) + } + fn parseIfStatement(&mut self) -> Result { let condition = if self.matchToken(&TokenKind::LeftParen) { let condition = self.parseExpression()?; @@ -1305,6 +1399,21 @@ impl Parser { false } } + + fn matchDoubleColon(&mut self) -> bool { + if self.check(&TokenKind::Colon) + && self + .tokens + .get(self.current + 1) + .is_some_and(|token| matches!(token.kind, TokenKind::Colon)) + { + self.advance(); + self.advance(); + true + } else { + false + } + } fn consume(&mut self, kind: &TokenKind, message: &str) -> Result<(), ParserError> { if self.check(kind) { self.advance(); diff --git a/compiler/v1/src/typecheck.rs b/compiler/v1/src/typecheck.rs index 6dc2499..8bf7b89 100644 --- a/compiler/v1/src/typecheck.rs +++ b/compiler/v1/src/typecheck.rs @@ -247,6 +247,7 @@ impl TypeChecker { for stmt in statements { if let Stmt::FuncDecl { name, + visibility: _, params, return_ty, .. @@ -357,6 +358,7 @@ impl TypeChecker { } Stmt::FuncDecl { name: _, + visibility: _, params, return_ty, body, @@ -514,6 +516,7 @@ impl TypeChecker { } Ok(()) } + Stmt::Import { .. } => Ok(()), Stmt::Break | Stmt::Continue => Ok(()), Stmt::Return(expr) => { let rty = self.exprTy(expr)?; From d0342107fb644f89a509b6b3119973fce1f20055 Mon Sep 17 00:00:00 2001 From: codex Date: Mon, 23 Mar 2026 01:32:21 +0530 Subject: [PATCH 3/8] feat(resolver): add file-level import resolution pipeline --- compiler/v1/src/imports.rs | 304 +++++++++++++++++++++++++++++++++++++ compiler/v1/src/main.rs | 72 +++------ 2 files changed, 326 insertions(+), 50 deletions(-) create mode 100644 compiler/v1/src/imports.rs diff --git a/compiler/v1/src/imports.rs b/compiler/v1/src/imports.rs new file mode 100644 index 0000000..28a3be6 --- /dev/null +++ b/compiler/v1/src/imports.rs @@ -0,0 +1,304 @@ +#![allow(non_snake_case)] + +use crate::ast::{FunctionVisibility, ImportKind, Stmt}; +use crate::lexer::{span::Span, Lexer, TokenKind}; +use crate::parser::Parser; +use std::collections::{HashMap, HashSet}; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; + +#[derive(Debug, Clone)] +pub struct CompileError { + pub title: String, + pub file: PathBuf, + pub source: String, + pub span: Span, + pub message: String, +} + +#[derive(Debug, Clone)] +pub struct ResolvedProgram { + pub statements: Vec, +} + +#[derive(Debug, Clone)] +struct ResolvedFile { + statements: Vec, + functionStatements: Vec, + localFunctionVisibility: HashMap, +} + +pub fn resolveEntry(entryPath: &Path) -> Result { + let canonicalEntry = canonicalPath(entryPath); + let projectRoot = canonicalEntry + .parent() + .unwrap_or_else(|| Path::new(".")) + .to_path_buf(); + let mut state = ResolverState::new(projectRoot); + let resolved = state.resolveFile(&canonicalEntry)?; + Ok(ResolvedProgram { + statements: resolved.statements, + }) +} + +struct ResolverState { + cache: HashMap, + stack: Vec, + projectRoot: PathBuf, +} + +impl ResolverState { + fn new(projectRoot: PathBuf) -> Self { + Self { + cache: HashMap::new(), + stack: Vec::new(), + projectRoot, + } + } + + fn resolveFile(&mut self, filePath: &Path) -> Result { + let filePath = canonicalPath(filePath); + if let Some(cached) = self.cache.get(&filePath) { + return Ok(cached.clone()); + } + + if self.stack.contains(&filePath) { + let mut chain = self + .stack + .iter() + .map(|p| p.display().to_string()) + .collect::>(); + chain.push(filePath.display().to_string()); + let source = fs::read_to_string(&filePath).unwrap_or_default(); + return Err(CompileError { + title: "import".to_string(), + file: filePath.clone(), + source, + span: Span::new(0, 1), + message: format!("Circular import detected: {}", chain.join(" -> ")), + }); + } + + self.stack.push(filePath.clone()); + let source = fs::read_to_string(&filePath).map_err(|_| CompileError { + title: "import".to_string(), + file: filePath.clone(), + source: String::new(), + span: Span::new(0, 1), + message: format!("File not found: '{}'", filePath.display()), + })?; + + let statements = parseSource(&filePath, &source)?; + let currentDir = filePath + .parent() + .unwrap_or_else(|| Path::new(".")) + .to_path_buf(); + + let mut localFunctionVisibility = HashMap::new(); + for stmt in &statements { + if let Stmt::FuncDecl { + name, visibility, .. + } = stmt + { + localFunctionVisibility.insert(name.clone(), *visibility); + } + } + + let mut resolved = Vec::new(); + let mut injectedNames = HashSet::new(); + let mut includedFiles = HashSet::new(); + for stmt in statements { + match stmt { + Stmt::Import { kind, span } => { + self.resolveImport( + &filePath, + &source, + ¤tDir, + kind, + span, + &mut resolved, + &mut injectedNames, + &mut includedFiles, + )?; + } + other => resolved.push(other), + } + } + + let functionStatements = resolved + .iter() + .filter(|stmt| matches!(stmt, Stmt::FuncDecl { .. })) + .cloned() + .collect::>(); + self.stack.pop(); + + let file = ResolvedFile { + statements: resolved, + functionStatements, + localFunctionVisibility, + }; + self.cache.insert(filePath, file.clone()); + Ok(file) + } + + #[allow(clippy::too_many_arguments)] + fn resolveImport( + &mut self, + ownerFile: &Path, + ownerSource: &str, + currentDir: &Path, + kind: ImportKind, + span: Span, + resolved: &mut Vec, + injectedNames: &mut HashSet, + includedFiles: &mut HashSet, + ) -> Result<(), CompileError> { + match kind { + ImportKind::FileSymbols { module, symbols } => { + let importFile = + self.findFileWithOrder(currentDir, &module, Some(ownerFile), ownerSource, span)?; + let imported = self.resolveFile(&importFile)?; + if !includedFiles.contains(&importFile) { + resolved.extend(imported.functionStatements.clone()); + includedFiles.insert(importFile.clone()); + } + + for symbol in symbols { + if !injectedNames.insert(symbol.clone()) { + return Err(CompileError { + title: "import".to_string(), + file: ownerFile.to_path_buf(), + source: ownerSource.to_string(), + span, + message: format!("Duplicate import: '{}'", symbol), + }); + } + match imported.localFunctionVisibility.get(&symbol) { + Some(FunctionVisibility::ExportPub) => {} + Some(FunctionVisibility::Pub) => { + return Err(CompileError { + title: "import".to_string(), + file: ownerFile.to_path_buf(), + source: ownerSource.to_string(), + span, + message: format!( + "Function '{}' exists in '{}' but is 'pub', not 'export pub'", + symbol, + importFile.display() + ), + }); + } + Some(FunctionVisibility::Private) | None => { + return Err(CompileError { + title: "import".to_string(), + file: ownerFile.to_path_buf(), + source: ownerSource.to_string(), + span, + message: format!( + "Function '{}' not found in file '{}'", + symbol, + importFile.display() + ), + }); + } + } + } + Ok(()) + } + ImportKind::ModuleNamespace { module } => Err(CompileError { + title: "import".to_string(), + file: ownerFile.to_path_buf(), + source: ownerSource.to_string(), + span, + message: format!( + "Module imports are not enabled yet for '{}'. Use file imports for now.", + module + ), + }), + ImportKind::ModuleItems { module, .. } => Err(CompileError { + title: "import".to_string(), + file: ownerFile.to_path_buf(), + source: ownerSource.to_string(), + span, + message: format!( + "Module imports are not enabled yet for '{}'. Use file imports for now.", + module + ), + }), + } + } + + fn findFileWithOrder( + &self, + currentDir: &Path, + module: &str, + ownerFile: Option<&Path>, + ownerSource: &str, + span: Span, + ) -> Result { + let mut candidates = Vec::new(); + candidates.push(currentDir.join(format!("{}.rey", module))); + candidates.push(self.projectRoot.join(format!("{}.rey", module))); + if let Some(home) = homePath() { + candidates.push(home.join(".reyc/packages").join(format!("{}.rey", module))); + } + + for candidate in candidates { + if candidate.exists() { + return Ok(canonicalPath(&candidate)); + } + } + + Err(CompileError { + title: "import".to_string(), + file: ownerFile + .map(|f| f.to_path_buf()) + .unwrap_or_else(|| PathBuf::from(module)), + source: ownerSource.to_string(), + span, + message: format!("File not found for import '{}.{}'", module, ""), + }) + } +} + +fn parseSource(filePath: &Path, source: &str) -> Result, CompileError> { + let mut lexer = Lexer::new(source); + let mut tokens = Vec::new(); + loop { + match lexer.nextToken() { + Ok(token) => { + tokens.push(token.clone()); + if token.kind == TokenKind::Eof { + break; + } + } + Err(err) => { + return Err(CompileError { + title: "lexer".to_string(), + file: filePath.to_path_buf(), + source: source.to_string(), + span: *err.span(), + message: err.message(), + }); + } + } + } + + let mut parser = Parser::new(tokens); + parser.parse().map_err(|err| CompileError { + title: "syntax".to_string(), + file: filePath.to_path_buf(), + source: source.to_string(), + span: *err.span(), + message: err.message(), + }) +} + +fn canonicalPath(path: &Path) -> PathBuf { + fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()) +} + +fn homePath() -> Option { + env::var("HOME").ok().map(PathBuf::from) +} diff --git a/compiler/v1/src/main.rs b/compiler/v1/src/main.rs index 4db2781..3cdd7d8 100644 --- a/compiler/v1/src/main.rs +++ b/compiler/v1/src/main.rs @@ -1,18 +1,24 @@ #![allow(non_snake_case)] mod ast; +mod imports; mod interpreter; mod lexer; mod parser; mod typecheck; +use imports::resolveEntry; use interpreter::Interpreter; -use lexer::{Lexer, TokenKind}; -use parser::Parser; use std::env; -use std::fs; +use std::path::PathBuf; -fn report_error(source: &str, span: &crate::lexer::span::Span, title: &str, message: &str) { +fn report_error( + filename: &str, + source: &str, + span: &crate::lexer::span::Span, + title: &str, + message: &str, +) { let mut line_num = 1; let mut line_start = 0; for (i, c) in source.char_indices() { @@ -40,7 +46,7 @@ fn report_error(source: &str, span: &crate::lexer::span::Span, title: &str, mess let red = "\x1b[1;31m"; let reset = "\x1b[0m"; eprintln!("{}error[{}]{}: {}", red, title, reset, message); - eprintln!(" --> line {}:{}", line_num, col + 1); + eprintln!(" --> {}:{}:{}", filename, line_num, col + 1); eprintln!(" |"); eprintln!("{:>2} | {}", line_num, line_str); eprintln!( @@ -53,20 +59,6 @@ fn report_error(source: &str, span: &crate::lexer::span::Span, title: &str, mess eprintln!(); } -fn report_error_in_file( - filename: &str, - source: &str, - span: &crate::lexer::span::Span, - title: &str, - message: &str, -) { - let red = "\x1b[1;31m"; - let reset = "\x1b[0m"; - eprintln!("{}error[{}]{}: {}", red, title, reset, message); - eprintln!(" --> {}", filename); - report_error(source, span, title, message); -} - fn main() { let args: Vec = env::args().collect(); let filename = if args.len() > 1 { @@ -81,43 +73,23 @@ fn main() { std::process::exit(1); } - let source = match fs::read_to_string(&filename) { - Ok(s) => s, - Err(_) => { - eprintln!("error: could not read file '{}'", filename); - std::process::exit(1); - } - }; - - let mut lexer = Lexer::new(&source); - let mut tokens = Vec::new(); - - loop { - match lexer.nextToken() { - Ok(token) => { - tokens.push(token.clone()); - if token.kind == TokenKind::Eof { - break; - } - } - Err(err) => { - report_error(&source, err.span(), "lexer", &err.message()); - std::process::exit(1); - } - } - } - - let mut parser = Parser::new(tokens); - match parser.parse() { - Ok(ast) => { + let entryPath = PathBuf::from(&filename); + match resolveEntry(&entryPath) { + Ok(program) => { let mut interpreter = Interpreter::new(); - if let Err(err) = interpreter.interpret(&ast) { + if let Err(err) = interpreter.interpret(&program.statements) { eprintln!("\x1b[1;31merror[runtime]\x1b[0m: {}", err); std::process::exit(1); } } Err(err) => { - report_error_in_file(&filename, &source, err.span(), "syntax", &err.message()); + report_error( + &err.file.display().to_string(), + &err.source, + &err.span, + &err.title, + &err.message, + ); std::process::exit(1); } } From ba1aee41fa65916bab8fd845624d3b5c446075d4 Mon Sep 17 00:00:00 2001 From: codex Date: Mon, 23 Mar 2026 01:34:05 +0530 Subject: [PATCH 4/8] feat(module): support module imports and auto-export collection --- compiler/v1/src/imports.rs | 388 ++++++++++++++++++++++++++++--------- 1 file changed, 295 insertions(+), 93 deletions(-) diff --git a/compiler/v1/src/imports.rs b/compiler/v1/src/imports.rs index 28a3be6..25855d7 100644 --- a/compiler/v1/src/imports.rs +++ b/compiler/v1/src/imports.rs @@ -1,6 +1,6 @@ #![allow(non_snake_case)] -use crate::ast::{FunctionVisibility, ImportKind, Stmt}; +use crate::ast::{Expr, FunctionVisibility, ImportKind, Stmt}; use crate::lexer::{span::Span, Lexer, TokenKind}; use crate::parser::Parser; use std::collections::{HashMap, HashSet}; @@ -63,81 +63,69 @@ impl ResolverState { return Ok(cached.clone()); } - if self.stack.contains(&filePath) { - let mut chain = self - .stack - .iter() - .map(|p| p.display().to_string()) - .collect::>(); - chain.push(filePath.display().to_string()); - let source = fs::read_to_string(&filePath).unwrap_or_default(); - return Err(CompileError { + // cycle checks are reported from import sites to preserve file/line context. + self.stack.push(filePath.clone()); + let result = (|| -> Result { + let source = fs::read_to_string(&filePath).map_err(|_| CompileError { title: "import".to_string(), file: filePath.clone(), - source, + source: String::new(), span: Span::new(0, 1), - message: format!("Circular import detected: {}", chain.join(" -> ")), - }); - } + message: format!("File not found: '{}'", filePath.display()), + })?; - self.stack.push(filePath.clone()); - let source = fs::read_to_string(&filePath).map_err(|_| CompileError { - title: "import".to_string(), - file: filePath.clone(), - source: String::new(), - span: Span::new(0, 1), - message: format!("File not found: '{}'", filePath.display()), - })?; - - let statements = parseSource(&filePath, &source)?; - let currentDir = filePath - .parent() - .unwrap_or_else(|| Path::new(".")) - .to_path_buf(); - - let mut localFunctionVisibility = HashMap::new(); - for stmt in &statements { - if let Stmt::FuncDecl { - name, visibility, .. - } = stmt - { - localFunctionVisibility.insert(name.clone(), *visibility); + let statements = parseSource(&filePath, &source)?; + let currentDir = filePath + .parent() + .unwrap_or_else(|| Path::new(".")) + .to_path_buf(); + + let mut localFunctionVisibility = HashMap::new(); + for stmt in &statements { + if let Stmt::FuncDecl { + name, visibility, .. + } = stmt + { + localFunctionVisibility.insert(name.clone(), *visibility); + } } - } - let mut resolved = Vec::new(); - let mut injectedNames = HashSet::new(); - let mut includedFiles = HashSet::new(); - for stmt in statements { - match stmt { - Stmt::Import { kind, span } => { - self.resolveImport( - &filePath, - &source, - ¤tDir, - kind, - span, - &mut resolved, - &mut injectedNames, - &mut includedFiles, - )?; + let mut resolved = Vec::new(); + let mut injectedNames = HashSet::new(); + let mut includedFiles = HashSet::new(); + for stmt in statements { + match stmt { + Stmt::Import { kind, span } => { + self.resolveImport( + &filePath, + &source, + ¤tDir, + kind, + span, + &mut resolved, + &mut injectedNames, + &mut includedFiles, + )?; + } + other => resolved.push(other), } - other => resolved.push(other), } - } - let functionStatements = resolved - .iter() - .filter(|stmt| matches!(stmt, Stmt::FuncDecl { .. })) - .cloned() - .collect::>(); + let functionStatements = resolved + .iter() + .filter(|stmt| matches!(stmt, Stmt::FuncDecl { .. })) + .cloned() + .collect::>(); + + Ok(ResolvedFile { + statements: resolved, + functionStatements, + localFunctionVisibility, + }) + })(); self.stack.pop(); - let file = ResolvedFile { - statements: resolved, - functionStatements, - localFunctionVisibility, - }; + let file = result?; self.cache.insert(filePath, file.clone()); Ok(file) } @@ -157,7 +145,10 @@ impl ResolverState { match kind { ImportKind::FileSymbols { module, symbols } => { let importFile = - self.findFileWithOrder(currentDir, &module, Some(ownerFile), ownerSource, span)?; + self.findFileImport(currentDir, &module, ownerFile, ownerSource, span)?; + if self.stack.contains(&importFile) { + return Err(self.circularError(ownerFile, ownerSource, span, &importFile)); + } let imported = self.resolveFile(&importFile)?; if !includedFiles.contains(&importFile) { resolved.extend(imported.functionStatements.clone()); @@ -189,7 +180,20 @@ impl ResolverState { ), }); } - Some(FunctionVisibility::Private) | None => { + Some(FunctionVisibility::Private) => { + return Err(CompileError { + title: "import".to_string(), + file: ownerFile.to_path_buf(), + source: ownerSource.to_string(), + span, + message: format!( + "Function '{}' exists in '{}' but is private", + symbol, + importFile.display() + ), + }); + } + None => { return Err(CompileError { title: "import".to_string(), file: ownerFile.to_path_buf(), @@ -206,34 +210,159 @@ impl ResolverState { } Ok(()) } - ImportKind::ModuleNamespace { module } => Err(CompileError { - title: "import".to_string(), - file: ownerFile.to_path_buf(), - source: ownerSource.to_string(), - span, - message: format!( - "Module imports are not enabled yet for '{}'. Use file imports for now.", - module - ), - }), - ImportKind::ModuleItems { module, .. } => Err(CompileError { - title: "import".to_string(), - file: ownerFile.to_path_buf(), - source: ownerSource.to_string(), - span, - message: format!( - "Module imports are not enabled yet for '{}'. Use file imports for now.", - module - ), - }), + ImportKind::ModuleNamespace { module } => { + if !injectedNames.insert(module.clone()) { + return Err(CompileError { + title: "import".to_string(), + file: ownerFile.to_path_buf(), + source: ownerSource.to_string(), + span, + message: format!("Duplicate import: '{}'", module), + }); + } + let moduleDir = + self.findModuleDir(currentDir, &module, ownerFile, ownerSource, span)?; + let moduleMain = moduleDir.join("main.rey"); + if !moduleMain.exists() { + return Err(CompileError { + title: "import".to_string(), + file: ownerFile.to_path_buf(), + source: ownerSource.to_string(), + span, + message: format!( + "Folder '{}' is not a module: missing main.rey", + moduleDir.display() + ), + }); + } + + let mut exportedSymbols = HashSet::new(); + let mut namespaceEntries = Vec::new(); + let mut moduleFiles = fs::read_dir(&moduleDir) + .map_err(|_| CompileError { + title: "import".to_string(), + file: ownerFile.to_path_buf(), + source: ownerSource.to_string(), + span, + message: format!("Could not read module folder '{}'", moduleDir.display()), + })? + .filter_map(|entry| entry.ok()) + .map(|entry| entry.path()) + .filter(|path| path.extension().is_some_and(|ext| ext == "rey")) + .collect::>(); + moduleFiles.sort(); + + for path in moduleFiles { + let path = canonicalPath(&path); + if self.stack.contains(&path) { + return Err(self.circularError(ownerFile, ownerSource, span, &path)); + } + let imported = self.resolveFile(&path)?; + if !includedFiles.contains(&path) { + resolved.extend(imported.functionStatements.clone()); + includedFiles.insert(path.clone()); + } + + for (name, visibility) in &imported.localFunctionVisibility { + if *visibility == FunctionVisibility::ExportPub { + if !exportedSymbols.insert(name.clone()) { + return Err(CompileError { + title: "import".to_string(), + file: ownerFile.to_path_buf(), + source: ownerSource.to_string(), + span, + message: format!( + "Duplicate exported function '{}' in module '{}'", + name, module + ), + }); + } + namespaceEntries.push((name.clone(), Expr::Variable(name.clone()))); + } + } + } + + resolved.push(self.namespaceStmt(&module, namespaceEntries)); + Ok(()) + } + ImportKind::ModuleItems { module, items } => { + for item in items { + if !injectedNames.insert(item.clone()) { + return Err(CompileError { + title: "import".to_string(), + file: ownerFile.to_path_buf(), + source: ownerSource.to_string(), + span, + message: format!("Duplicate import: '{}'", item), + }); + } + + let importFile = self.findModuleItemFile( + currentDir, + &module, + &item, + ownerFile, + ownerSource, + span, + )?; + if self.stack.contains(&importFile) { + return Err(self.circularError(ownerFile, ownerSource, span, &importFile)); + } + let imported = self.resolveFile(&importFile)?; + if !includedFiles.contains(&importFile) { + resolved.extend(imported.functionStatements.clone()); + includedFiles.insert(importFile.clone()); + } + + let mut namespaceEntries = Vec::new(); + for (name, visibility) in imported.localFunctionVisibility { + if visibility == FunctionVisibility::ExportPub { + namespaceEntries.push((name.clone(), Expr::Variable(name))); + } + } + resolved.push(self.namespaceStmt(&item, namespaceEntries)); + } + Ok(()) + } } } - fn findFileWithOrder( + fn circularError( + &self, + ownerFile: &Path, + ownerSource: &str, + span: Span, + target: &Path, + ) -> CompileError { + let mut chain = self + .stack + .iter() + .map(|p| p.display().to_string()) + .collect::>(); + chain.push(target.display().to_string()); + CompileError { + title: "import".to_string(), + file: ownerFile.to_path_buf(), + source: ownerSource.to_string(), + span, + message: format!("Circular import detected: {}", chain.join(" -> ")), + } + } + + fn namespaceStmt(&self, name: &str, entries: Vec<(String, Expr)>) -> Stmt { + Stmt::VarDecl { + is_const: true, + name: name.to_string(), + ty: None, + initializer: Expr::DictLiteral { entries }, + } + } + + fn findFileImport( &self, currentDir: &Path, module: &str, - ownerFile: Option<&Path>, + ownerFile: &Path, ownerSource: &str, span: Span, ) -> Result { @@ -252,12 +381,85 @@ impl ResolverState { Err(CompileError { title: "import".to_string(), - file: ownerFile - .map(|f| f.to_path_buf()) - .unwrap_or_else(|| PathBuf::from(module)), + file: ownerFile.to_path_buf(), + source: ownerSource.to_string(), + span, + message: format!("File not found for import '{}.rey'", module), + }) + } + + fn findModuleDir( + &self, + currentDir: &Path, + module: &str, + ownerFile: &Path, + ownerSource: &str, + span: Span, + ) -> Result { + let mut candidates = Vec::new(); + candidates.push(currentDir.join(module)); + candidates.push(self.projectRoot.join(module)); + if module == "std" { + if let Some(home) = homePath() { + candidates.push(home.join(".reyc/std/src")); + } + } + if let Some(home) = homePath() { + candidates.push(home.join(".reyc/packages").join(module)); + } + + for candidate in candidates { + if candidate.is_dir() { + return Ok(canonicalPath(&candidate)); + } + } + + Err(CompileError { + title: "import".to_string(), + file: ownerFile.to_path_buf(), + source: ownerSource.to_string(), + span, + message: format!("Module folder not found: '{}'", module), + }) + } + + fn findModuleItemFile( + &self, + currentDir: &Path, + module: &str, + item: &str, + ownerFile: &Path, + ownerSource: &str, + span: Span, + ) -> Result { + let mut candidates = Vec::new(); + candidates.push(currentDir.join(module).join(format!("{}.rey", item))); + candidates.push(self.projectRoot.join(module).join(format!("{}.rey", item))); + if module == "std" { + if let Some(home) = homePath() { + candidates.push(home.join(".reyc/std/src").join(format!("{}.rey", item))); + } + } + if let Some(home) = homePath() { + candidates.push( + home.join(".reyc/packages") + .join(module) + .join(format!("{}.rey", item)), + ); + } + + for candidate in candidates { + if candidate.exists() { + return Ok(canonicalPath(&candidate)); + } + } + + Err(CompileError { + title: "import".to_string(), + file: ownerFile.to_path_buf(), source: ownerSource.to_string(), span, - message: format!("File not found for import '{}.{}'", module, ""), + message: format!("File not found for module import '{}::{}'", module, item), }) } } From 3d2184e067f8f73e45ceeab4641c1bb8b54d4546 Mon Sep 17 00:00:00 2001 From: codex Date: Mon, 23 Mar 2026 01:36:52 +0530 Subject: [PATCH 5/8] feat(scope): support namespace function dispatch for imports --- compiler/v1/src/interpreter/executor.rs | 30 ++++++++++++++++++++++++- compiler/v1/src/typecheck.rs | 1 + 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/compiler/v1/src/interpreter/executor.rs b/compiler/v1/src/interpreter/executor.rs index 44c4b98..d20b0fb 100644 --- a/compiler/v1/src/interpreter/executor.rs +++ b/compiler/v1/src/interpreter/executor.rs @@ -344,7 +344,7 @@ impl Executor { env, ); } - self.evaluate_method_call(recv, name, &evaluated_args) + self.evaluate_method_call(recv, name, &evaluated_args, env) } Expr::StructLiteral { name, @@ -704,8 +704,36 @@ impl Executor { receiver: Value, name: &str, args: &[Value], + env: &mut Environment, ) -> Result { match (receiver, name) { + (Value::Dict(d), method_name) => { + let value = d + .borrow() + .get(method_name) + .cloned() + .ok_or_else(|| format!("Namespace function '{}' not found", method_name))?; + match value { + Value::Function(func) => { + if args.len() != func.arity() { + return Err(format!( + "Expected {} arguments but got {}", + func.arity(), + args.len() + )); + } + let mut function_env = Environment::with_parent(env.clone()); + for (param, arg_value) in func.params.iter().zip(args.iter()) { + function_env.define(param.name.clone(), arg_value.clone()); + } + self.execute_block(&func.body, &mut function_env) + } + _ => Err(format!( + "Namespace member '{}' is not callable", + method_name + )), + } + } (val, "toString") => { if !args.is_empty() { return Err(format!( diff --git a/compiler/v1/src/typecheck.rs b/compiler/v1/src/typecheck.rs index 8bf7b89..e315638 100644 --- a/compiler/v1/src/typecheck.rs +++ b/compiler/v1/src/typecheck.rs @@ -962,6 +962,7 @@ impl TypeChecker { } Ok(Ty::Array(Box::new(Ty::String))) } + (Ty::Dict(_, _), _) => Ok(Ty::Any), _ => Err(TypeError { message: format!( "Type error: method '{}' not supported on {:?}", From 9765ba723c2b3038fd71b9a6acfed5088ac418d6 Mon Sep 17 00:00:00 2001 From: codex Date: Mon, 23 Mar 2026 01:37:02 +0530 Subject: [PATCH 6/8] test(imports): add file and module import coverage fixtures --- tests/imports/README.md | 13 +++++++++++++ tests/imports/errors/actuator.rey | 7 +++++++ tests/imports/errors/circular/cycle_a.rey | 5 +++++ tests/imports/errors/circular/cycle_b.rey | 5 +++++ tests/imports/errors/circular/cycle_entry.rey | 3 +++ tests/imports/errors/duplicate_import.rey | 4 ++++ tests/imports/errors/file_not_found.rey | 3 +++ tests/imports/errors/folder_missing_main.rey | 3 +++ tests/imports/errors/function_not_found.rey | 3 +++ tests/imports/errors/missingmod/walk.rey | 3 +++ tests/imports/errors/pub_not_export.rey | 3 +++ tests/imports/success/action/main.rey | 3 +++ tests/imports/success/action/run.rey | 3 +++ tests/imports/success/action/walk.rey | 3 +++ tests/imports/success/actuator.rey | 15 +++++++++++++++ tests/imports/success/main.rey | 11 +++++++++++ 16 files changed, 87 insertions(+) create mode 100644 tests/imports/README.md create mode 100644 tests/imports/errors/actuator.rey create mode 100644 tests/imports/errors/circular/cycle_a.rey create mode 100644 tests/imports/errors/circular/cycle_b.rey create mode 100644 tests/imports/errors/circular/cycle_entry.rey create mode 100644 tests/imports/errors/duplicate_import.rey create mode 100644 tests/imports/errors/file_not_found.rey create mode 100644 tests/imports/errors/folder_missing_main.rey create mode 100644 tests/imports/errors/function_not_found.rey create mode 100644 tests/imports/errors/missingmod/walk.rey create mode 100644 tests/imports/errors/pub_not_export.rey create mode 100644 tests/imports/success/action/main.rey create mode 100644 tests/imports/success/action/run.rey create mode 100644 tests/imports/success/action/walk.rey create mode 100644 tests/imports/success/actuator.rey create mode 100644 tests/imports/success/main.rey diff --git a/tests/imports/README.md b/tests/imports/README.md new file mode 100644 index 0000000..10b0b8f --- /dev/null +++ b/tests/imports/README.md @@ -0,0 +1,13 @@ +Import system tests for Rey. + +Run from `compiler/v1`: + +```bash +cargo run -- ../../tests/imports/success/main.rey +cargo run -- ../../tests/imports/errors/file_not_found.rey +cargo run -- ../../tests/imports/errors/folder_missing_main.rey +cargo run -- ../../tests/imports/errors/function_not_found.rey +cargo run -- ../../tests/imports/errors/pub_not_export.rey +cargo run -- ../../tests/imports/errors/circular/cycle_entry.rey +cargo run -- ../../tests/imports/errors/duplicate_import.rey +``` diff --git a/tests/imports/errors/actuator.rey b/tests/imports/errors/actuator.rey new file mode 100644 index 0000000..a64d9fa --- /dev/null +++ b/tests/imports/errors/actuator.rey @@ -0,0 +1,7 @@ +export pub func name() { + println("ok"); +} + +pub func onlyPub() { + println("pub-only"); +} diff --git a/tests/imports/errors/circular/cycle_a.rey b/tests/imports/errors/circular/cycle_a.rey new file mode 100644 index 0000000..40b4992 --- /dev/null +++ b/tests/imports/errors/circular/cycle_a.rey @@ -0,0 +1,5 @@ +import cycle_b.ping; + +export pub func start() { + ping(); +} diff --git a/tests/imports/errors/circular/cycle_b.rey b/tests/imports/errors/circular/cycle_b.rey new file mode 100644 index 0000000..4d1d068 --- /dev/null +++ b/tests/imports/errors/circular/cycle_b.rey @@ -0,0 +1,5 @@ +import cycle_a.start; + +export pub func ping() { + start(); +} diff --git a/tests/imports/errors/circular/cycle_entry.rey b/tests/imports/errors/circular/cycle_entry.rey new file mode 100644 index 0000000..ed95ed8 --- /dev/null +++ b/tests/imports/errors/circular/cycle_entry.rey @@ -0,0 +1,3 @@ +import cycle_a.start; + +start(); diff --git a/tests/imports/errors/duplicate_import.rey b/tests/imports/errors/duplicate_import.rey new file mode 100644 index 0000000..5faa8f5 --- /dev/null +++ b/tests/imports/errors/duplicate_import.rey @@ -0,0 +1,4 @@ +import actuator.name; +import actuator.name; + +println("should-not-run"); diff --git a/tests/imports/errors/file_not_found.rey b/tests/imports/errors/file_not_found.rey new file mode 100644 index 0000000..9f7eb5b --- /dev/null +++ b/tests/imports/errors/file_not_found.rey @@ -0,0 +1,3 @@ +import missing.name; + +println("should-not-run"); diff --git a/tests/imports/errors/folder_missing_main.rey b/tests/imports/errors/folder_missing_main.rey new file mode 100644 index 0000000..7de3e06 --- /dev/null +++ b/tests/imports/errors/folder_missing_main.rey @@ -0,0 +1,3 @@ +import missingmod; + +println("should-not-run"); diff --git a/tests/imports/errors/function_not_found.rey b/tests/imports/errors/function_not_found.rey new file mode 100644 index 0000000..d293452 --- /dev/null +++ b/tests/imports/errors/function_not_found.rey @@ -0,0 +1,3 @@ +import actuator.nope; + +println("should-not-run"); diff --git a/tests/imports/errors/missingmod/walk.rey b/tests/imports/errors/missingmod/walk.rey new file mode 100644 index 0000000..683ba13 --- /dev/null +++ b/tests/imports/errors/missingmod/walk.rey @@ -0,0 +1,3 @@ +export pub func hi() { + return "hi"; +} diff --git a/tests/imports/errors/pub_not_export.rey b/tests/imports/errors/pub_not_export.rey new file mode 100644 index 0000000..591770d --- /dev/null +++ b/tests/imports/errors/pub_not_export.rey @@ -0,0 +1,3 @@ +import actuator.onlyPub; + +println("should-not-run"); diff --git a/tests/imports/success/action/main.rey b/tests/imports/success/action/main.rey new file mode 100644 index 0000000..e298712 --- /dev/null +++ b/tests/imports/success/action/main.rey @@ -0,0 +1,3 @@ +export pub func entry() { + return "entry"; +} diff --git a/tests/imports/success/action/run.rey b/tests/imports/success/action/run.rey new file mode 100644 index 0000000..c076881 --- /dev/null +++ b/tests/imports/success/action/run.rey @@ -0,0 +1,3 @@ +export pub func go() { + return "run-go"; +} diff --git a/tests/imports/success/action/walk.rey b/tests/imports/success/action/walk.rey new file mode 100644 index 0000000..8d24197 --- /dev/null +++ b/tests/imports/success/action/walk.rey @@ -0,0 +1,3 @@ +export pub func step() { + return "walk-step"; +} diff --git a/tests/imports/success/actuator.rey b/tests/imports/success/actuator.rey new file mode 100644 index 0000000..10964b9 --- /dev/null +++ b/tests/imports/success/actuator.rey @@ -0,0 +1,15 @@ +export pub func name() { + println("name"); +} + +export pub func other() { + println("other"); +} + +pub func onlyPub() { + println("pub-only"); +} + +func privateHelper() { + println("private"); +} diff --git a/tests/imports/success/main.rey b/tests/imports/success/main.rey new file mode 100644 index 0000000..f0cbf29 --- /dev/null +++ b/tests/imports/success/main.rey @@ -0,0 +1,11 @@ +import actuator.name; +import actuator.{other}; +import action; +import action::walk; +import action::{run}; + +name(); +other(); +println(action.entry()); +println(walk.step()); +println(run.go()); From 603bc4bfeb9b11d854e3c1051f2b43d7521346cd Mon Sep 17 00:00:00 2001 From: codex Date: Mon, 23 Mar 2026 01:37:59 +0530 Subject: [PATCH 7/8] chore(codex): update primer and changelog --- CHANGELOG.md | 32 +++++++++ primer.md | 186 ++++++++++++++++++--------------------------------- 2 files changed, 97 insertions(+), 121 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abd5258..54bfc62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # Changelog +## [feature] — 2026-03-23 +- Implemented full import system for Rey with compile-time resolution. +- Added `export pub` function modifier and import visibility enforcement: + - `export pub func` => importable + - `pub func` => not importable + - `func` => private +- Added parser/AST support for: + - `import file.symbol` + - `import file.{a,b}` + - `import module` + - `import module::file` + - `import module::{a,b}` +- Added import resolver pipeline (`compiler/v1/src/imports.rs`) and integrated it into compiler entry flow. +- Implemented resolver lookup order: + 1. current file directory + 2. project root (entry file directory) + 3. `~/.reyc/std/src` for `std` prefix + 4. `~/.reyc/packages` +- Added module semantics: + - `module/main.rey` required for `import module` + - module namespace auto-collects all `export pub` functions from `.rey` files in folder + - `module::file` resolves direct file namespace +- Added import diagnostics for: + - file not found + - missing module `main.rey` + - function not found + - function is `pub` but not `export pub` + - circular imports + - duplicate imports +- Added runtime/typecheck namespace dispatch support for `namespace.func()`. +- Added import fixtures under `tests/imports/` for success and all required error cases. + ## [release] — 2026-03-19 - Shipped `rey v0.0.7-pre` - Added `projects/fake-cli/cli.rey` - a full interactive TUI implementation in Rey. diff --git a/primer.md b/primer.md index 601fcb4..5b73cd1 100644 --- a/primer.md +++ b/primer.md @@ -1,129 +1,73 @@ # Primer — rey-lang -Last updated: Mar 19, 2026 (session end) +Last updated: Mar 23, 2026 (session end) ## What this project is -Rey is a custom programming language built by Misbah. Currently on v0 — a tree-walking interpreter written in Rust. The language has C-like syntax with type inference, functions, control flow, and basic builtins. v0 is the working prototype; future versions will likely move toward compilation. +Rey is a custom language by Misbah. Current runtime is a Rust tree-walking interpreter (`compiler/v1`) with compile-time parsing/typechecking and runtime execution. ## Key architecture ``` compiler/v1/src/ -├── lexer/ — tokenizer (cursor, token, span, error) -├── parser/ — produces AST (parser.rs, error.rs) -├── ast/ — AST node types (expr, stmt, literal, ty) -└── interpreter/ — tree-walker (evaluator, executor, environment, value, function, std, control_flow) +├── lexer/ # tokenizer + spans +├── parser/ # recursive descent parser + parse errors +├── ast/ # expressions/statements/types +├── typecheck.rs # static checks +└── interpreter/ # executor/evaluator/environment ``` -Pipeline: source → lexer → tokens → parser → AST → interpreter → output - -## Build & run -```bash -cd compiler/v1 -cargo build --release -./target/release/rey-v0 .rey - -cargo run -- src/tests/variables.rey -``` - -## What's implemented in v0 - -### Variables & Types -- Variables with optional type annotations (`var x = 10`, `var x: int = 10`) -- Immutable variables with const (`const pi: float = 3.14`) -- Unannotated variables use dynamic typing (`Ty::Any`) -- Types: `int`, `float`, `String`, `bool`, `char`, `null`, `Void` -- Additional numeric types: `uint`, `double`, `byte` -- Nullable types (`int?`) for null safety - -### Operators -- Arithmetic: `+`, `-`, `*`, `/`, `%` (modulo) -- Comparison: `==`, `!=`, `<`, `<=`, `>`, `>=` -- Logical: `&&`, `||`, `!` -- Assignment: `=`, `+=`, `-=`, `*=`, `/=`, `%=` -- Increment/decrement: `++`, `--` (prefix and postfix) -- `instanceof` for type checking - -### Control Flow -- `if`/`else if`/`else` — conditional branching -- `while` — standard while loop -- `loop` — infinite loop with `break` -- `for x in range(start, end)` — range iteration -- `for x in array` — array element iteration -- `break`, `continue` — loop control -- `match` — pattern matching on enums and primitives - -### Functions -- Function declarations with optional typed params and return types -- Default parameter values -- Variadic parameters -- Lambda expressions: `(x: int, y: int) => x + y` -- First-class functions (functions as values) - -### Data Structures -- **Arrays**: literals `[1, 2, 3]`, indexing `arr[0]`, typed arrays `[int]` - - Methods: `length()`, `push()`, `pop()` -- **Dictionaries**: literals `{"key": value}`, indexing `dict["key"]` - - Typed dicts: `{String:int}` -- **Tuples**: literals `(1, "a", true)`, access via index `.0`, `.1` -- **Structs**: definitions, literals, static/instance methods - - Field scoping (fields act as method's "global scope") - - `pub`/`private` visibility for methods - -### Enums -```rey -enum Direction { - North, - South, - East, - West -} - -var dir = North; // Enum variants are automatically defined -match dir { - Direction::North => print("Going north"), - Direction::South => print("Going south"), - _ => print("Unknown") -} -``` - -### Strings -- String interpolation: `"Value: {var}"` -- Mixed concatenation: `"HP: " + 10` -- Methods: `length()`, `upper()`, `lower()`, `contains()`, `split()`, `toString()`, `toInt()`, `toFloat()` - -### Builtins -- `print()`, `println()` — output -- `len()` — length of arrays/strings -- `push()`, `pop()` — array operations -- `input()` — read user input -- `abs()`, `max()`, `min()` — math functions -- `random()` — random number generation - -### Error Handling -- Compile-time type checking for annotated vars/functions -- Rust/Miette-like visual error diagnostics -- Runtime error messages with context - -### Entry Point -- If `main()` function exists, it's called automatically - -## Test files -`tests/` directory contains `.rey` files for each feature: -- `field_assign.rey` — external field assignment -- `array_index_assign.rey` — array index assignment -- `int_div.rey` — integer division -- `loop.rey` — infinite loop keyword -- `for_in_array.rey` — for-in array iteration -- `enum_match.rey` — enums and match statements - -Run any test: `cargo run -- ../../tests/.rey` - -## Current status -`rey v0.0.7-pre` complete on `codex` branch: -- All v0.0.7 features implemented -- Bugs fixed: else if chaining, field assignment, array index assignment, integer division -- New features: `loop` keyword, `for x in array`, enums, match statements - -## Next up (future releases) -- Build and package binaries for macOS (arm64) and Windows (x86_64) -- Add null safety: `null` comparisons and clean error on `null` access -- Add `try`/`catch` error handling -- Update `syntax.md` documentation +Pipeline: source -> lexer -> parser -> AST -> typecheck -> interpreter + +## Session completed +- Added new function visibility model in AST/parser/lexer: + - `export pub func` => importable + - `pub func` => local/module visibility but blocked from imports + - `func` => private +- Added import AST and parser support: + - `import file.symbol` + - `import file.{a,b}` + - `import module` + - `import module::file` + - `import module::{fileA,fileB}` +- Added compile-time import resolver (`compiler/v1/src/imports.rs`) and integrated it into `main.rs`. +- Implemented resolver order: + 1. current file directory + 2. entry project root + 3. `~/.reyc/std/src` for `std` module prefix + 4. `~/.reyc/packages` +- Implemented module rules: + - `import action` requires `action/main.rey` + - module namespace auto-collects `export pub` symbols from every `.rey` file in that folder + - `import action::walk` resolves `action/walk.rey` +- Implemented scope injection: + - file-symbol imports inject names directly + - module imports inject namespace dicts (`action.func()`, `walk.func()`) +- Implemented diagnostics for: + - file not found + - missing module `main.rey` + - function not found + - function exists but only `pub` + - circular imports (with cycle chain) + - duplicate imports +- Added namespace method-call dispatch in executor/typechecker for imported namespace calls. + +## Tests added +- `tests/imports/success/` full passing integration case with file and module import forms. +- `tests/imports/errors/` covers all required error categories: + - missing file + - missing module main + - missing function + - `pub` not `export pub` + - circular import + - duplicate import + +## Verification run this session +- `cargo build` (pass) +- `cargo test` (pass) +- `cargo run -- ../../tests/imports/success/main.rey` (pass) +- `cargo run -- ../../tests/imports/errors/*.rey` (expected compile-time failures, all correct category/messages) + +## Current project state +- Import system is fully implemented for the requested spec. +- Branch has five logical commits for parser/visibility, resolver, modules, scope dispatch, and tests. + +## Next up +- Add automated Rust integration tests that execute the new import fixtures and assert expected stdout/stderr. +- Add docs update in `syntax.md` describing import grammar and `export pub` rules. From c29eacf6ee85a6319ef91f6a4aa6733fe8d6ff94 Mon Sep 17 00:00:00 2001 From: codex Date: Mon, 23 Mar 2026 01:52:39 +0530 Subject: [PATCH 8/8] fix(rebase): align import resolver with current ast and lexer --- compiler/v1/src/imports.rs | 21 +++++++++++++++------ compiler/v1/src/lexer/lexer.rs | 1 - compiler/v1/src/lexer/token.rs | 1 - 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/compiler/v1/src/imports.rs b/compiler/v1/src/imports.rs index 25855d7..270697f 100644 --- a/compiler/v1/src/imports.rs +++ b/compiler/v1/src/imports.rs @@ -277,12 +277,18 @@ impl ResolverState { ), }); } - namespaceEntries.push((name.clone(), Expr::Variable(name.clone()))); + namespaceEntries.push(( + name.clone(), + Expr::Variable { + name: name.clone(), + span, + }, + )); } } } - resolved.push(self.namespaceStmt(&module, namespaceEntries)); + resolved.push(self.namespaceStmt(&module, namespaceEntries, span)); Ok(()) } ImportKind::ModuleItems { module, items } => { @@ -317,10 +323,13 @@ impl ResolverState { let mut namespaceEntries = Vec::new(); for (name, visibility) in imported.localFunctionVisibility { if visibility == FunctionVisibility::ExportPub { - namespaceEntries.push((name.clone(), Expr::Variable(name))); + namespaceEntries.push(( + name.clone(), + Expr::Variable { name, span }, + )); } } - resolved.push(self.namespaceStmt(&item, namespaceEntries)); + resolved.push(self.namespaceStmt(&item, namespaceEntries, span)); } Ok(()) } @@ -349,12 +358,12 @@ impl ResolverState { } } - fn namespaceStmt(&self, name: &str, entries: Vec<(String, Expr)>) -> Stmt { + fn namespaceStmt(&self, name: &str, entries: Vec<(String, Expr)>, span: Span) -> Stmt { Stmt::VarDecl { is_const: true, name: name.to_string(), ty: None, - initializer: Expr::DictLiteral { entries }, + initializer: Expr::DictLiteral { entries, span }, } } diff --git a/compiler/v1/src/lexer/lexer.rs b/compiler/v1/src/lexer/lexer.rs index 73ebfed..c4a1f54 100644 --- a/compiler/v1/src/lexer/lexer.rs +++ b/compiler/v1/src/lexer/lexer.rs @@ -389,7 +389,6 @@ impl<'a> Lexer<'a> { "break" => TokenKind::Break, "continue" => TokenKind::Continue, "struct" => TokenKind::Struct, - "pub" => TokenKind::Pub, "self" => TokenKind::SelfKw, "true" => TokenKind::True, "false" => TokenKind::False, diff --git a/compiler/v1/src/lexer/token.rs b/compiler/v1/src/lexer/token.rs index 8fd87cf..46b2a8e 100644 --- a/compiler/v1/src/lexer/token.rs +++ b/compiler/v1/src/lexer/token.rs @@ -66,7 +66,6 @@ pub enum TokenKind { False, Null, Struct, - Pub, SelfKw, //literals