Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
21f62247f5
24 changed files with 643 additions and 131 deletions
|
|
@ -240,6 +240,7 @@ pub enum ExprKind {
|
||||||
Return(Option<Box<Expr>>),
|
Return(Option<Box<Expr>>),
|
||||||
Break,
|
Break,
|
||||||
Continue,
|
Continue,
|
||||||
|
Defer(Box<Expr>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
|
@ -273,7 +274,7 @@ pub enum UnOp {
|
||||||
Not,
|
Not,
|
||||||
Ref,
|
Ref,
|
||||||
Deref,
|
Deref,
|
||||||
PreInc
|
PreInc,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -429,6 +430,7 @@ pub enum TypedExprKind {
|
||||||
Return(Option<Box<TypedExpr>>),
|
Return(Option<Box<TypedExpr>>),
|
||||||
Break,
|
Break,
|
||||||
Continue,
|
Continue,
|
||||||
|
Defer(Box<TypedExpr>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ pub enum CUnaryOp {
|
||||||
Not,
|
Not,
|
||||||
Ref,
|
Ref,
|
||||||
Deref,
|
Deref,
|
||||||
PreInc
|
PreInc,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CUnaryOp {
|
impl CUnaryOp {
|
||||||
|
|
@ -132,7 +132,7 @@ impl CUnaryOp {
|
||||||
CUnaryOp::Not => "!",
|
CUnaryOp::Not => "!",
|
||||||
CUnaryOp::Ref => "&",
|
CUnaryOp::Ref => "&",
|
||||||
CUnaryOp::Deref => "*",
|
CUnaryOp::Deref => "*",
|
||||||
CUnaryOp::PreInc => "++"
|
CUnaryOp::PreInc => "++",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -187,8 +187,20 @@ impl StatementsTranspiler {
|
||||||
}
|
}
|
||||||
TypedExprKind::Do(exprs) => {
|
TypedExprKind::Do(exprs) => {
|
||||||
let mut stmts = Vec::new();
|
let mut stmts = Vec::new();
|
||||||
|
let mut defers = Vec::new();
|
||||||
for expr in exprs {
|
for expr in exprs {
|
||||||
stmts.push(self.transpile_stmt(expr)?);
|
match &expr.kind {
|
||||||
|
TypedExprKind::Defer(defer_expr) => {
|
||||||
|
defers.push(self.transpile_stmt(defer_expr)?);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
stmts.push(self.transpile_stmt(expr)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Execute defers in reverse order at the end
|
||||||
|
for defer_stmt in defers.into_iter().rev() {
|
||||||
|
stmts.push(defer_stmt);
|
||||||
}
|
}
|
||||||
Ok(CStmt::Block(stmts))
|
Ok(CStmt::Block(stmts))
|
||||||
}
|
}
|
||||||
|
|
@ -211,10 +223,8 @@ impl StatementsTranspiler {
|
||||||
Box::new(end_expr),
|
Box::new(end_expr),
|
||||||
);
|
);
|
||||||
|
|
||||||
let incr = CExpr::UnOp(
|
let incr =
|
||||||
CUnaryOp::PreInc,
|
CExpr::UnOp(CUnaryOp::PreInc, Box::new(CExpr::Var(var_name.clone())));
|
||||||
Box::new(CExpr::Var(var_name.clone())),
|
|
||||||
);
|
|
||||||
|
|
||||||
let body_stmts = self.expr_to_stmts(body)?;
|
let body_stmts = self.expr_to_stmts(body)?;
|
||||||
Ok(CStmt::For(init, cond, incr, body_stmts))
|
Ok(CStmt::For(init, cond, incr, body_stmts))
|
||||||
|
|
@ -224,32 +234,26 @@ impl StatementsTranspiler {
|
||||||
TypedExprKind::Variable(var) => {
|
TypedExprKind::Variable(var) => {
|
||||||
let elem_ty = match &iterable.ty {
|
let elem_ty = match &iterable.ty {
|
||||||
Type::Array(inner) => self.type_to_ctype(inner)?,
|
Type::Array(inner) => self.type_to_ctype(inner)?,
|
||||||
_ => return Err(format!(
|
_ => {
|
||||||
"Cannot iterate over non-array variable `{}`",
|
return Err(format!(
|
||||||
var
|
"Cannot iterate over non-array variable `{}`",
|
||||||
)),
|
var
|
||||||
|
));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// i = 0
|
// i = 0
|
||||||
let (idx_name, idx_decl) = self.fresh_tmp_var(
|
let (idx_name, idx_decl) =
|
||||||
"_i",
|
self.fresh_tmp_var("_i", CType::Int, Some(CExpr::IntLit(0)));
|
||||||
CType::Int,
|
|
||||||
Some(CExpr::IntLit(0)),
|
|
||||||
);
|
|
||||||
|
|
||||||
let cond = CExpr::BinOp(
|
let cond = CExpr::BinOp(
|
||||||
Box::new(CExpr::Var(idx_name.clone())),
|
Box::new(CExpr::Var(idx_name.clone())),
|
||||||
CBinaryOp::Lt,
|
CBinaryOp::Lt,
|
||||||
Box::new(CExpr::Dot(
|
Box::new(CExpr::Dot(Box::new(CExpr::Var(var.clone())), "len".into())),
|
||||||
Box::new(CExpr::Var(var.clone())),
|
|
||||||
"len".into(),
|
|
||||||
)),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let incr = CExpr::UnOp(
|
let incr =
|
||||||
CUnaryOp::PreInc,
|
CExpr::UnOp(CUnaryOp::PreInc, Box::new(CExpr::Var(idx_name.clone())));
|
||||||
Box::new(CExpr::Var(idx_name.clone())),
|
|
||||||
);
|
|
||||||
|
|
||||||
// let x = array.data[i]
|
// let x = array.data[i]
|
||||||
let bind = CStmt::VarDecl(CVarDecl {
|
let bind = CStmt::VarDecl(CVarDecl {
|
||||||
|
|
@ -292,11 +296,8 @@ impl StatementsTranspiler {
|
||||||
);
|
);
|
||||||
|
|
||||||
// i = 0
|
// i = 0
|
||||||
let (idx_name, idx_decl) = self.fresh_tmp_var(
|
let (idx_name, idx_decl) =
|
||||||
"_i",
|
self.fresh_tmp_var("_i", CType::Int, Some(CExpr::IntLit(0)));
|
||||||
CType::Int,
|
|
||||||
Some(CExpr::IntLit(0)),
|
|
||||||
);
|
|
||||||
|
|
||||||
let cond = CExpr::BinOp(
|
let cond = CExpr::BinOp(
|
||||||
Box::new(CExpr::Var(idx_name.clone())),
|
Box::new(CExpr::Var(idx_name.clone())),
|
||||||
|
|
@ -304,10 +305,8 @@ impl StatementsTranspiler {
|
||||||
Box::new(CExpr::IntLit(arr_len as i64)),
|
Box::new(CExpr::IntLit(arr_len as i64)),
|
||||||
);
|
);
|
||||||
|
|
||||||
let incr = CExpr::UnOp(
|
let incr =
|
||||||
CUnaryOp::PreInc,
|
CExpr::UnOp(CUnaryOp::PreInc, Box::new(CExpr::Var(idx_name.clone())));
|
||||||
Box::new(CExpr::Var(idx_name.clone())),
|
|
||||||
);
|
|
||||||
|
|
||||||
let bind = CStmt::VarDecl(CVarDecl {
|
let bind = CStmt::VarDecl(CVarDecl {
|
||||||
name: var_name.clone(),
|
name: var_name.clone(),
|
||||||
|
|
@ -332,6 +331,7 @@ impl StatementsTranspiler {
|
||||||
}
|
}
|
||||||
TypedExprKind::Break => Ok(CStmt::Break),
|
TypedExprKind::Break => Ok(CStmt::Break),
|
||||||
TypedExprKind::Continue => Ok(CStmt::Continue),
|
TypedExprKind::Continue => Ok(CStmt::Continue),
|
||||||
|
TypedExprKind::Defer(_) => Err("Defer should be handled in Do blocks".to_string()),
|
||||||
_ => {
|
_ => {
|
||||||
// For other expressions, treat as expression statements
|
// For other expressions, treat as expression statements
|
||||||
let c_expr = self.transpile_expr(expr)?;
|
let c_expr = self.transpile_expr(expr)?;
|
||||||
|
|
@ -344,21 +344,36 @@ impl StatementsTranspiler {
|
||||||
match &expr.kind {
|
match &expr.kind {
|
||||||
TypedExprKind::Do(stmts) => {
|
TypedExprKind::Do(stmts) => {
|
||||||
let mut c_stmts = Vec::new();
|
let mut c_stmts = Vec::new();
|
||||||
|
let mut defers = Vec::new();
|
||||||
|
let mut last_stmt = None;
|
||||||
for (i, stmt) in stmts.iter().enumerate() {
|
for (i, stmt) in stmts.iter().enumerate() {
|
||||||
if i == stmts.len() - 1 {
|
match &stmt.kind {
|
||||||
// Last expression in a block should be returned
|
TypedExprKind::Defer(defer_expr) => {
|
||||||
match &stmt.kind {
|
defers.push(self.transpile_stmt(defer_expr)?);
|
||||||
TypedExprKind::Return(_) => {
|
}
|
||||||
|
_ => {
|
||||||
|
if i == stmts.len() - 1 {
|
||||||
|
last_stmt = Some(stmt);
|
||||||
|
} else {
|
||||||
c_stmts.push(self.transpile_stmt(stmt)?);
|
c_stmts.push(self.transpile_stmt(stmt)?);
|
||||||
}
|
}
|
||||||
_ => {
|
|
||||||
// Convert to return statement
|
|
||||||
let c_expr = self.transpile_expr(stmt)?;
|
|
||||||
c_stmts.push(CStmt::Return(Some(c_expr)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
c_stmts.push(self.transpile_stmt(stmt)?);
|
}
|
||||||
|
// Execute defers in reverse order before the last statement
|
||||||
|
for defer_stmt in defers.into_iter().rev() {
|
||||||
|
c_stmts.push(defer_stmt);
|
||||||
|
}
|
||||||
|
if let Some(last) = last_stmt {
|
||||||
|
match &last.kind {
|
||||||
|
TypedExprKind::Return(_) => {
|
||||||
|
c_stmts.push(self.transpile_stmt(last)?);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Convert to return statement
|
||||||
|
let c_expr = self.transpile_expr(last)?;
|
||||||
|
c_stmts.push(CStmt::Return(Some(c_expr)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(c_stmts)
|
Ok(c_stmts)
|
||||||
|
|
@ -402,12 +417,8 @@ impl StatementsTranspiler {
|
||||||
Type::Bool => Ok(CType::Bool),
|
Type::Bool => Ok(CType::Bool),
|
||||||
Type::String => Ok(CType::Ptr(Box::new(CType::Char))),
|
Type::String => Ok(CType::Ptr(Box::new(CType::Char))),
|
||||||
Type::Unit => Ok(CType::Void),
|
Type::Unit => Ok(CType::Void),
|
||||||
Type::Ptr(inner) => {
|
Type::Ptr(inner) => Ok(CType::Ptr(Box::new(self.type_to_ctype(inner)?))),
|
||||||
Ok(CType::Ptr(Box::new(self.type_to_ctype(inner)?)))
|
Type::Array(inner) => Ok(CType::Ptr(Box::new(self.type_to_ctype(inner)?))),
|
||||||
}
|
|
||||||
Type::Array(inner) => {
|
|
||||||
Ok(CType::Ptr(Box::new(self.type_to_ctype(inner)?)))
|
|
||||||
}
|
|
||||||
Type::Struct(name, _) => Ok(CType::Struct(name.clone())),
|
Type::Struct(name, _) => Ok(CType::Struct(name.clone())),
|
||||||
Type::Enum(name, _) => Ok(CType::Struct(name.clone())),
|
Type::Enum(name, _) => Ok(CType::Struct(name.clone())),
|
||||||
Type::Tuple(types) => {
|
Type::Tuple(types) => {
|
||||||
|
|
@ -480,8 +491,7 @@ impl StatementsTranspiler {
|
||||||
}
|
}
|
||||||
let c_ret = self.type_annot_to_ctype(ret)?;
|
let c_ret = self.type_annot_to_ctype(ret)?;
|
||||||
Ok(CType::Func(c_args, Box::new(c_ret)))
|
Ok(CType::Func(c_args, Box::new(c_ret)))
|
||||||
}
|
} //_ => Err(format!("Unsupported type annotation: {:?}", annot)),
|
||||||
//_ => Err(format!("Unsupported type annotation: {:?}", annot)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::ast::*;
|
use crate::ast::*;
|
||||||
use crate::c_ir::*;
|
use crate::c_ir::*;
|
||||||
use std::collections::HashMap;
|
|
||||||
use crate::c_lowerer::declaration_transpiler::DeclarationTranspiler;
|
use crate::c_lowerer::declaration_transpiler::DeclarationTranspiler;
|
||||||
use crate::c_lowerer::statements_transpiler::StatementsTranspiler;
|
use crate::c_lowerer::statements_transpiler::StatementsTranspiler;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub struct Transpiler {
|
pub struct Transpiler {
|
||||||
structs: HashMap<String, CStructDecl>,
|
structs: HashMap<String, CStructDecl>,
|
||||||
|
|
@ -99,7 +99,7 @@ impl Transpiler {
|
||||||
// But for now, skip as they're handled differently
|
// But for now, skip as they're handled differently
|
||||||
}
|
}
|
||||||
TypedASTNodeKind::Load(_) => {}
|
TypedASTNodeKind::Load(_) => {}
|
||||||
TypedASTNodeKind::Trait(typed_trait ) => {}
|
TypedASTNodeKind::Trait(typed_trait) => {}
|
||||||
TypedASTNodeKind::Use(_) => {}
|
TypedASTNodeKind::Use(_) => {}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -227,19 +227,20 @@ impl Transpiler {
|
||||||
}
|
}
|
||||||
|
|
||||||
CStmt::For(init, cond, incr, body) => {
|
CStmt::For(init, cond, incr, body) => {
|
||||||
let mut output = format!("for ({}; {}; {}) {{", self.generate_var_decl(init), self.generate_expr(cond), self.generate_expr(incr));
|
let mut output = format!(
|
||||||
|
"for ({}; {}; {}) {{",
|
||||||
|
self.generate_var_decl(init),
|
||||||
|
self.generate_expr(cond),
|
||||||
|
self.generate_expr(incr)
|
||||||
|
);
|
||||||
for stmt in body {
|
for stmt in body {
|
||||||
output.push_str(&format!(" {}", self.generate_stmt(stmt)));
|
output.push_str(&format!(" {}", self.generate_stmt(stmt)));
|
||||||
}
|
}
|
||||||
output.push_str(" }\n");
|
output.push_str(" }\n");
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
CStmt::Break => {
|
CStmt::Break => "break;\n".to_string(),
|
||||||
"break;\n".to_string()
|
CStmt::Continue => "continue;\n".to_string(),
|
||||||
}
|
|
||||||
CStmt::Continue => {
|
|
||||||
"continue;\n".to_string()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,7 +314,10 @@ impl Transpiler {
|
||||||
}
|
}
|
||||||
|
|
||||||
CExpr::ArrayLit(array_lit) => {
|
CExpr::ArrayLit(array_lit) => {
|
||||||
let vec = array_lit.iter().map(|expr| self.generate_expr(expr)).collect::<Vec<_>>();
|
let vec = array_lit
|
||||||
|
.iter()
|
||||||
|
.map(|expr| self.generate_expr(expr))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
let len = vec.len();
|
let len = vec.len();
|
||||||
format!("{}[{}]{{ {} }}", vec[0], len, vec.join(", "))
|
format!("{}[{}]{{ {} }}", vec[0], len, vec.join(", "))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
410
src/import_resolver.rs
Normal file
410
src/import_resolver.rs
Normal file
|
|
@ -0,0 +1,410 @@
|
||||||
|
use crate::ast::{ASTNode, ASTNodeKind, Span};
|
||||||
|
use crate::lexer::Token;
|
||||||
|
use crate::parser::Parser;
|
||||||
|
use logos::Logos;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
const EXTENSION: &str = ".sui";
|
||||||
|
|
||||||
|
/// Error types for import resolution
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ImportError {
|
||||||
|
DuplicateSymbol {
|
||||||
|
symbol: String,
|
||||||
|
locations: Vec<String>,
|
||||||
|
},
|
||||||
|
FileNotFound {
|
||||||
|
path: String,
|
||||||
|
reason: String,
|
||||||
|
},
|
||||||
|
LexError {
|
||||||
|
file: String,
|
||||||
|
position: usize,
|
||||||
|
},
|
||||||
|
ParseError {
|
||||||
|
file: String,
|
||||||
|
message: String,
|
||||||
|
span: (usize, usize),
|
||||||
|
},
|
||||||
|
CircularDependency {
|
||||||
|
files: Vec<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ImportError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ImportError::DuplicateSymbol { symbol, locations } => {
|
||||||
|
write!(f, "Duplicate global symbol '{}' defined in:\n", symbol)?;
|
||||||
|
for (i, loc) in locations.iter().enumerate() {
|
||||||
|
write!(f, " [{}] {}", i + 1, loc)?;
|
||||||
|
if i < locations.len() - 1 {
|
||||||
|
writeln!(f)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
ImportError::FileNotFound { path, reason } => {
|
||||||
|
write!(f, "Module not found '{}': {}", path, reason)
|
||||||
|
}
|
||||||
|
ImportError::LexError { file, position } => {
|
||||||
|
write!(f, "Lexer error at position {} in {}", position, file)
|
||||||
|
}
|
||||||
|
ImportError::ParseError {
|
||||||
|
file,
|
||||||
|
message,
|
||||||
|
span,
|
||||||
|
} => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Parse error in {}: {} (at {}..{})",
|
||||||
|
file, message, span.0, span.1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ImportError::CircularDependency { files } => {
|
||||||
|
write!(f, "Circular dependency detected: {}", files.join(" -> "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ImportError {}
|
||||||
|
|
||||||
|
/// Cached parse output for a file
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct ParsedFile {
|
||||||
|
/// AST nodes from this file (before import collection)
|
||||||
|
nodes: Vec<ASTNode>,
|
||||||
|
/// Hash of the file content at parse time
|
||||||
|
content_hash: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tracks global symbols and their definitions
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct GlobalSymbolRegistry {
|
||||||
|
/// Maps symbol name to (file, span) where it was defined
|
||||||
|
symbols: HashMap<String, Vec<(String, Span)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalSymbolRegistry {
|
||||||
|
fn register(&mut self, symbol: String, file: String, span: Span) -> Result<(), ImportError> {
|
||||||
|
self.symbols.entry(symbol.clone()).or_insert_with(Vec::new);
|
||||||
|
let locations = self.symbols.get_mut(&symbol).unwrap();
|
||||||
|
|
||||||
|
locations.push((file.clone(), span));
|
||||||
|
|
||||||
|
// Check for duplicates
|
||||||
|
if locations.len() > 1 {
|
||||||
|
let location_strs = locations
|
||||||
|
.iter()
|
||||||
|
.map(|(f, s)| format!("{}:{}..{}", f, s.start, s.end))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
return Err(ImportError::DuplicateSymbol {
|
||||||
|
symbol,
|
||||||
|
locations: location_strs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Manages file dependency resolution and caching
|
||||||
|
pub struct ImportResolver {
|
||||||
|
/// Cache of parsed files keyed by path
|
||||||
|
parse_cache: HashMap<String, ParsedFile>,
|
||||||
|
/// Global symbol registry for duplicate detection
|
||||||
|
symbol_registry: GlobalSymbolRegistry,
|
||||||
|
/// Set of files currently being processed (for cycle detection)
|
||||||
|
processing_stack: HashSet<String>,
|
||||||
|
/// Dependency graph: file -> list of files it depends on
|
||||||
|
dependency_graph: HashMap<String, HashSet<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImportResolver {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
ImportResolver {
|
||||||
|
parse_cache: HashMap::new(),
|
||||||
|
symbol_registry: GlobalSymbolRegistry::default(),
|
||||||
|
processing_stack: HashSet::new(),
|
||||||
|
dependency_graph: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute a simple hash of file content
|
||||||
|
fn hash_content(content: &str) -> u64 {
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
content.hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lex source code into tokens
|
||||||
|
fn lex_source(source: &str, filename: &str) -> Result<Vec<(Token, logos::Span)>, ImportError> {
|
||||||
|
let mut tokens = Vec::new();
|
||||||
|
let mut lexer = Token::lexer(source);
|
||||||
|
|
||||||
|
while let Some(token_result) = lexer.next() {
|
||||||
|
match token_result {
|
||||||
|
Ok(token) => {
|
||||||
|
tokens.push((token, lexer.span()));
|
||||||
|
}
|
||||||
|
Err(()) => {
|
||||||
|
return Err(ImportError::LexError {
|
||||||
|
file: filename.to_string(),
|
||||||
|
position: lexer.span().start,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse tokens into AST
|
||||||
|
fn parse_tokens(
|
||||||
|
tokens: Vec<(Token, logos::Span)>,
|
||||||
|
filename: String,
|
||||||
|
) -> Result<Vec<ASTNode>, ImportError> {
|
||||||
|
let mut parser = Parser::new(filename.clone(), tokens);
|
||||||
|
parser.parse().map_err(|e| ImportError::ParseError {
|
||||||
|
file: filename,
|
||||||
|
message: e.message.clone(),
|
||||||
|
span: (e.span.start, e.span.end),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a file (using cache if content hash matches)
|
||||||
|
fn parse_file(&mut self, filename: &str) -> Result<Vec<ASTNode>, ImportError> {
|
||||||
|
let source = fs::read_to_string(filename).map_err(|e| ImportError::FileNotFound {
|
||||||
|
path: filename.to_string(),
|
||||||
|
reason: e.to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let content_hash = Self::hash_content(&source);
|
||||||
|
|
||||||
|
// Check if we have a valid cached version
|
||||||
|
if let Some(cached) = self.parse_cache.get(filename) {
|
||||||
|
if cached.content_hash == content_hash {
|
||||||
|
println!(" [cache hit] {}", filename);
|
||||||
|
return Ok(cached.nodes.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the file
|
||||||
|
println!(" [parsing] {}", filename);
|
||||||
|
let tokens = Self::lex_source(&source, filename)?;
|
||||||
|
let nodes = Self::parse_tokens(tokens, filename.to_string())?;
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
self.parse_cache.insert(
|
||||||
|
filename.to_string(),
|
||||||
|
ParsedFile {
|
||||||
|
nodes: nodes.clone(),
|
||||||
|
content_hash,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collect all dependencies from a file (via Use/Load statements)
|
||||||
|
fn collect_dependencies(&self, filename: &str, nodes: &[ASTNode]) -> Vec<String> {
|
||||||
|
let mut deps = Vec::new();
|
||||||
|
let mut seen = HashSet::new();
|
||||||
|
|
||||||
|
for node in nodes {
|
||||||
|
if let ASTNodeKind::Use(use_stmt) = &node.kind {
|
||||||
|
let path = &use_stmt.path;
|
||||||
|
if !seen.contains(path) {
|
||||||
|
seen.insert(path.clone());
|
||||||
|
deps.push(path.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve relative paths
|
||||||
|
let importing_dir = Path::new(filename).parent().unwrap_or(Path::new(""));
|
||||||
|
|
||||||
|
deps.iter()
|
||||||
|
.filter_map(|path| {
|
||||||
|
let resolved = if path.starts_with("std/") {
|
||||||
|
Some(format!("src/{}{}", path, EXTENSION))
|
||||||
|
} else if path.starts_with("@") {
|
||||||
|
// Package manager support - TODO
|
||||||
|
None
|
||||||
|
} else if path.starts_with(".") || path.starts_with("~") {
|
||||||
|
let resolved_path = importing_dir.join(path);
|
||||||
|
Some(resolved_path.to_string_lossy().to_string() + EXTENSION)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
resolved
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check for circular dependencies
|
||||||
|
fn detect_cycles(&self, file: &str, target: &str) -> Result<(), ImportError> {
|
||||||
|
if file == target {
|
||||||
|
return Err(ImportError::CircularDependency {
|
||||||
|
files: vec![file.to_string()],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple cycle detection: if target depends on file, we have a cycle
|
||||||
|
if let Some(target_deps) = self.dependency_graph.get(target) {
|
||||||
|
if target_deps.contains(file) {
|
||||||
|
return Err(ImportError::CircularDependency {
|
||||||
|
files: vec![file.to_string(), target.to_string()],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively resolve imports for a file
|
||||||
|
fn resolve_imports_recursive(&mut self, filename: &str) -> Result<Vec<ASTNode>, ImportError> {
|
||||||
|
// Check for cycles in processing
|
||||||
|
if self.processing_stack.contains(filename) {
|
||||||
|
return Err(ImportError::CircularDependency {
|
||||||
|
files: self
|
||||||
|
.processing_stack
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_iter()
|
||||||
|
.chain(std::iter::once(filename.to_string()))
|
||||||
|
.collect(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.processing_stack.insert(filename.to_string());
|
||||||
|
|
||||||
|
// Parse the file
|
||||||
|
let nodes = self.parse_file(filename)?;
|
||||||
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
// Collect dependencies
|
||||||
|
let deps = self.collect_dependencies(filename, &nodes);
|
||||||
|
self.dependency_graph
|
||||||
|
.insert(filename.to_string(), deps.iter().cloned().collect());
|
||||||
|
|
||||||
|
// Process each node
|
||||||
|
for node in nodes {
|
||||||
|
match &node.kind {
|
||||||
|
ASTNodeKind::Use(use_stmt) => {
|
||||||
|
let path = &use_stmt.path;
|
||||||
|
|
||||||
|
// Resolve path
|
||||||
|
let dep_file = if path.starts_with("std/") {
|
||||||
|
format!("src/{}{}", path, EXTENSION)
|
||||||
|
} else if path.starts_with("@") {
|
||||||
|
// Package manager support - TODO
|
||||||
|
todo!("Package manager imports not yet supported")
|
||||||
|
} else if path.starts_with(".") || path.starts_with("~") {
|
||||||
|
let importing_dir = Path::new(filename).parent().unwrap_or(Path::new(""));
|
||||||
|
let resolved_path = importing_dir.join(path);
|
||||||
|
resolved_path.to_string_lossy().to_string() + EXTENSION
|
||||||
|
} else {
|
||||||
|
return Err(ImportError::FileNotFound {
|
||||||
|
path: path.clone(),
|
||||||
|
reason: "Invalid import path format".to_string(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check for cycles
|
||||||
|
self.detect_cycles(filename, &dep_file)?;
|
||||||
|
|
||||||
|
// Recursively resolve the imported file
|
||||||
|
let imported_nodes = self.resolve_imports_recursive(&dep_file)?;
|
||||||
|
result.extend(imported_nodes);
|
||||||
|
}
|
||||||
|
ASTNodeKind::Struct(s) => {
|
||||||
|
// Register the symbol
|
||||||
|
self.symbol_registry.register(
|
||||||
|
s.name.clone(),
|
||||||
|
filename.to_string(),
|
||||||
|
node.span.clone(),
|
||||||
|
)?;
|
||||||
|
result.push(node);
|
||||||
|
}
|
||||||
|
ASTNodeKind::Enum(e) => {
|
||||||
|
self.symbol_registry.register(
|
||||||
|
e.name.clone(),
|
||||||
|
filename.to_string(),
|
||||||
|
node.span.clone(),
|
||||||
|
)?;
|
||||||
|
result.push(node);
|
||||||
|
}
|
||||||
|
ASTNodeKind::Function(f) => {
|
||||||
|
self.symbol_registry.register(
|
||||||
|
f.name.clone(),
|
||||||
|
filename.to_string(),
|
||||||
|
node.span.clone(),
|
||||||
|
)?;
|
||||||
|
result.push(node);
|
||||||
|
}
|
||||||
|
ASTNodeKind::Impl(_) => {
|
||||||
|
// Impl blocks don't have names but are still tracked
|
||||||
|
result.push(node);
|
||||||
|
}
|
||||||
|
ASTNodeKind::Extern(e) => {
|
||||||
|
self.symbol_registry.register(
|
||||||
|
e.name.clone(),
|
||||||
|
filename.to_string(),
|
||||||
|
node.span.clone(),
|
||||||
|
)?;
|
||||||
|
result.push(node);
|
||||||
|
}
|
||||||
|
ASTNodeKind::Load(_) => {
|
||||||
|
result.push(node);
|
||||||
|
}
|
||||||
|
ASTNodeKind::Trait(t) => {
|
||||||
|
self.symbol_registry.register(
|
||||||
|
t.name.clone(),
|
||||||
|
filename.to_string(),
|
||||||
|
node.span.clone(),
|
||||||
|
)?;
|
||||||
|
result.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.processing_stack.remove(filename);
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve all imports starting from the given file
|
||||||
|
pub fn resolve(&mut self, filename: &str) -> Result<Vec<ASTNode>, ImportError> {
|
||||||
|
println!("Starting import resolution...");
|
||||||
|
self.resolve_imports_recursive(filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hash_content() {
|
||||||
|
let content1 = "fn main() do end";
|
||||||
|
let content2 = "fn main() do end";
|
||||||
|
let content3 = "fn main() { x }";
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
ImportResolver::hash_content(content1),
|
||||||
|
ImportResolver::hash_content(content2)
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
ImportResolver::hash_content(content1),
|
||||||
|
ImportResolver::hash_content(content3)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -145,6 +145,9 @@ impl LambdaLowerer {
|
||||||
self.collect_free_vars_expr(start, lambda_params, free_vars, local_scope);
|
self.collect_free_vars_expr(start, lambda_params, free_vars, local_scope);
|
||||||
self.collect_free_vars_expr(end, lambda_params, free_vars, local_scope);
|
self.collect_free_vars_expr(end, lambda_params, free_vars, local_scope);
|
||||||
}
|
}
|
||||||
|
ExprKind::Defer(expr) => {
|
||||||
|
self.collect_free_vars_expr(expr, lambda_params, free_vars, local_scope);
|
||||||
|
}
|
||||||
// Terminal expressions don't contain variables
|
// Terminal expressions don't contain variables
|
||||||
ExprKind::Int(_)
|
ExprKind::Int(_)
|
||||||
| ExprKind::Float(_)
|
| ExprKind::Float(_)
|
||||||
|
|
@ -372,6 +375,7 @@ impl LambdaLowerer {
|
||||||
| ExprKind::Variable(_)
|
| ExprKind::Variable(_)
|
||||||
| ExprKind::Break
|
| ExprKind::Break
|
||||||
| ExprKind::Continue => expr.kind.clone(),
|
| ExprKind::Continue => expr.kind.clone(),
|
||||||
|
ExprKind::Defer(inner) => ExprKind::Defer(Box::new(self.lower_expr(inner)?)),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Expr {
|
Ok(Expr {
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,9 @@ pub enum Token {
|
||||||
#[token("continue")]
|
#[token("continue")]
|
||||||
KeywordContinue,
|
KeywordContinue,
|
||||||
|
|
||||||
|
#[token("defer")]
|
||||||
|
KeywordDefer,
|
||||||
|
|
||||||
#[token("+")]
|
#[token("+")]
|
||||||
Plus,
|
Plus,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ pub const EXTENSION: &str = ".sui";
|
||||||
|
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
pub mod c_ir;
|
pub mod c_ir;
|
||||||
pub mod codegen;
|
|
||||||
pub mod c_lowerer;
|
pub mod c_lowerer;
|
||||||
|
pub mod codegen;
|
||||||
|
pub mod import_resolver;
|
||||||
pub mod lambda_lower;
|
pub mod lambda_lower;
|
||||||
pub mod lexer;
|
pub mod lexer;
|
||||||
pub mod monomorphize;
|
pub mod monomorphize;
|
||||||
|
|
|
||||||
93
src/main.rs
93
src/main.rs
|
|
@ -1,30 +1,40 @@
|
||||||
use logos::Logos;
|
use clap::Parser;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use suicmez::{
|
use suicmez::{
|
||||||
codegen::transpiler::Transpiler,
|
codegen::transpiler::Transpiler,
|
||||||
lambda_lower::LambdaLowerer,
|
import_resolver::ImportResolver,
|
||||||
lexer::Token,
|
lambda_lower::LambdaLowerer,
|
||||||
monomorphize::{Monomorphizer, check_no_typevars},
|
monomorphize::{Monomorphizer, check_no_typevars},
|
||||||
parser::Parser,
|
typechecker::TypeChecker,
|
||||||
typechecker::TypeChecker,
|
};
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
#[derive(Parser)]
|
||||||
// Check if a file was provided as argument
|
#[command(author, version, about = "A compiler for the Sui language")]
|
||||||
let args: Vec<String> = std::env::args().collect();
|
struct Args {
|
||||||
if args.len() < 2 {
|
/// Run the test suite instead of compiling a file
|
||||||
// Run all test files in the tests directory
|
#[arg(short, long)]
|
||||||
run_test_suite();
|
test: bool,
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let filename = &args[1];
|
/// The Sui source file to compile
|
||||||
println!("Type checking file: {}", filename);
|
file: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(e) = run_file(filename) {
|
fn main() {
|
||||||
eprintln!("Error: {}", e);
|
let args = Args::parse();
|
||||||
}
|
|
||||||
}
|
if args.test {
|
||||||
|
run_test_suite();
|
||||||
|
} else if let Some(filename) = args.file {
|
||||||
|
println!("Type checking file: {}", filename);
|
||||||
|
|
||||||
|
if let Err(e) = run_file(&filename) {
|
||||||
|
eprintln!("Error: {}", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("No file specified. Use --test to run the test suite or provide a Sui file to compile.");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn run_test_suite() {
|
fn run_test_suite() {
|
||||||
println!("Running test suite...\n");
|
println!("Running test suite...\n");
|
||||||
|
|
@ -164,34 +174,19 @@ fn format_type_error(source: &str, error: &suicmez::typechecker::TypeError) -> S
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_file(filename: &str) -> Result<(), String> {
|
fn run_file(filename: &str) -> Result<(), String> {
|
||||||
// Read the source file
|
// ========== IMPORT RESOLUTION PHASE ==========
|
||||||
|
println!("\n=== Import Resolution Phase ===");
|
||||||
|
let mut resolver = ImportResolver::new();
|
||||||
|
let ast_nodes = resolver
|
||||||
|
.resolve(filename)
|
||||||
|
.map_err(|e| format!("Import resolution error: {}", e))?;
|
||||||
|
|
||||||
|
println!("Import resolution complete! {} total nodes loaded", ast_nodes.len());
|
||||||
|
|
||||||
|
// Read the source file for error reporting
|
||||||
let source = fs::read_to_string(filename)
|
let source = fs::read_to_string(filename)
|
||||||
.map_err(|e| format!("Error reading file {}: {}", filename, e))?;
|
.map_err(|e| format!("Error reading file {}: {}", filename, e))?;
|
||||||
|
|
||||||
// First, we need to parse the source code
|
|
||||||
let mut tokens = Vec::new();
|
|
||||||
let mut lexer = Token::lexer(&source);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match lexer.next() {
|
|
||||||
Some(Ok(token)) => {
|
|
||||||
let span = lexer.span();
|
|
||||||
tokens.push((token, span));
|
|
||||||
}
|
|
||||||
Some(Err(_)) => {
|
|
||||||
return Err("Lexing error".to_string());
|
|
||||||
}
|
|
||||||
None => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut parser = Parser::new(filename.to_string(), tokens);
|
|
||||||
let ast_nodes = parser
|
|
||||||
.parse()
|
|
||||||
.map_err(|e| format_parse_error(&source, &e))?;
|
|
||||||
|
|
||||||
println!("Parsed {} AST nodes successfully", ast_nodes.len());
|
|
||||||
|
|
||||||
// Lower lambdas to generated functions
|
// Lower lambdas to generated functions
|
||||||
let lowerer = LambdaLowerer::new();
|
let lowerer = LambdaLowerer::new();
|
||||||
let lowered_nodes = lowerer
|
let lowered_nodes = lowerer
|
||||||
|
|
|
||||||
|
|
@ -327,6 +327,11 @@ impl Monomorphizer {
|
||||||
| TypedExprKind::String(_)
|
| TypedExprKind::String(_)
|
||||||
| TypedExprKind::Break
|
| TypedExprKind::Break
|
||||||
| TypedExprKind::Continue => expr.kind.clone(),
|
| TypedExprKind::Continue => expr.kind.clone(),
|
||||||
|
TypedExprKind::Defer(inner) => {
|
||||||
|
let (new_inner, mut inner_needs) = self.monomorphize_expr(inner)?;
|
||||||
|
needs.append(&mut inner_needs);
|
||||||
|
TypedExprKind::Defer(Box::new(new_inner))
|
||||||
|
}
|
||||||
|
|
||||||
TypedExprKind::Array(elems) => {
|
TypedExprKind::Array(elems) => {
|
||||||
let mut new_elems = Vec::new();
|
let mut new_elems = Vec::new();
|
||||||
|
|
|
||||||
|
|
@ -1720,7 +1720,18 @@ impl Parser {
|
||||||
self.next();
|
self.next();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
exprs.push(self.parse_expr()?);
|
if matches!(self.peek(), Some(Token::KeywordDefer)) {
|
||||||
|
self.next();
|
||||||
|
let expr = self.parse_expr()?;
|
||||||
|
let end = expr.span.end;
|
||||||
|
exprs.push(Expr {
|
||||||
|
kind: ExprKind::Defer(Box::new(expr)),
|
||||||
|
span: Span::new(&(start..end), self.file.clone()),
|
||||||
|
attributes: Vec::new(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
exprs.push(self.parse_expr()?);
|
||||||
|
}
|
||||||
|
|
||||||
if matches!(self.peek(), Some(Token::Semicolon)) {
|
if matches!(self.peek(), Some(Token::Semicolon)) {
|
||||||
self.next();
|
self.next();
|
||||||
|
|
|
||||||
|
|
@ -1410,6 +1410,11 @@ impl TypeChecker {
|
||||||
ExprKind::Break => (TypedExprKind::Break, Type::Never),
|
ExprKind::Break => (TypedExprKind::Break, Type::Never),
|
||||||
ExprKind::Continue => (TypedExprKind::Continue, Type::Never),
|
ExprKind::Continue => (TypedExprKind::Continue, Type::Never),
|
||||||
|
|
||||||
|
ExprKind::Defer(expr) => {
|
||||||
|
let typed_expr = self.typecheck_expr(expr)?;
|
||||||
|
(TypedExprKind::Defer(Box::new(typed_expr)), Type::Unit)
|
||||||
|
}
|
||||||
|
|
||||||
ExprKind::EarlyReturn(value) => {
|
ExprKind::EarlyReturn(value) => {
|
||||||
let typed_value = if let Some(v) = value {
|
let typed_value = if let Some(v) = value {
|
||||||
Some(Box::new(self.typecheck_expr(v)?))
|
Some(Box::new(self.typecheck_expr(v)?))
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
int main(void);
|
int main(void);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
18
tests/functions.c
Normal file
18
tests/functions.c
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
int add(int x, int y);
|
||||||
|
int main(void);
|
||||||
|
|
||||||
|
|
||||||
|
int add(int x, int y) {
|
||||||
|
return (x + y);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
int sum = add(5, 3);
|
||||||
|
struct T id = identity(42);
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
6
tests/import_tests/duplicate_main.sui
Normal file
6
tests/import_tests/duplicate_main.sui
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
use "./module_a"
|
||||||
|
use "./module_b"
|
||||||
|
|
||||||
|
fn main() -> int do
|
||||||
|
0
|
||||||
|
end
|
||||||
20
tests/import_tests/good_import.c
Normal file
20
tests/import_tests/good_import.c
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
struct Point {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
};
|
||||||
|
int helper_func(void);
|
||||||
|
int main(void);
|
||||||
|
|
||||||
|
|
||||||
|
int helper_func(void) {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
return helper_func();
|
||||||
|
}
|
||||||
|
|
||||||
5
tests/import_tests/good_import.sui
Normal file
5
tests/import_tests/good_import.sui
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
use "./util"
|
||||||
|
|
||||||
|
fn main() -> int do
|
||||||
|
helper_func()
|
||||||
|
end
|
||||||
3
tests/import_tests/module_a.sui
Normal file
3
tests/import_tests/module_a.sui
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn test_func() -> int do
|
||||||
|
42
|
||||||
|
end
|
||||||
3
tests/import_tests/module_b.sui
Normal file
3
tests/import_tests/module_b.sui
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn test_func() -> int do
|
||||||
|
99
|
||||||
|
end
|
||||||
8
tests/import_tests/util.sui
Normal file
8
tests/import_tests/util.sui
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
fn helper_func() -> int do
|
||||||
|
42
|
||||||
|
end
|
||||||
|
|
||||||
|
struct Point
|
||||||
|
x: int,
|
||||||
|
y: int,
|
||||||
|
end
|
||||||
|
|
@ -2,12 +2,10 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
struct Point {
|
struct Point {
|
||||||
int x;
|
int x;
|
||||||
int y;
|
int y;
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(void);
|
int main(void);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,21 @@
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
struct Number {
|
struct Number {
|
||||||
int value;
|
int value;
|
||||||
};
|
};
|
||||||
|
char* Number_show(struct Number self);
|
||||||
char *Number_show(struct Number self);
|
|
||||||
int main(void);
|
int main(void);
|
||||||
|
|
||||||
char *Number_show(struct Number self) { return "number"; }
|
|
||||||
|
char* Number_show(struct Number self) {
|
||||||
|
return "number";
|
||||||
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
struct Number number = (struct Number){.value = 40};
|
struct Number number = (struct Number){ .value = 40 };
|
||||||
Number_show(number);
|
Number_show(number);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue