diff --git a/crates/concrete_ast/src/modules.rs b/crates/concrete_ast/src/modules.rs index 490c966..584d32d 100644 --- a/crates/concrete_ast/src/modules.rs +++ b/crates/concrete_ast/src/modules.rs @@ -11,6 +11,7 @@ use crate::{ #[derive(Clone, Debug, Eq, PartialEq)] pub struct Module { pub doc_string: Option, + pub external_modules: Vec, pub imports: Vec, pub name: Ident, pub contents: Vec, diff --git a/crates/concrete_check/src/lib.rs b/crates/concrete_check/src/lib.rs index 2ef11a9..25a3d44 100644 --- a/crates/concrete_check/src/lib.rs +++ b/crates/concrete_check/src/lib.rs @@ -1,12 +1,13 @@ -use std::{ops::Range, path::PathBuf}; +use std::ops::Range; use ariadne::{ColorGenerator, Label, Report, ReportKind}; use concrete_ir::lowering::errors::LoweringError; +use concrete_session::Session; /// Creates a report from a lowering error. pub fn lowering_error_to_report( error: LoweringError, - file_paths: &[PathBuf], + session: &Session, ) -> Report<'static, (String, Range)> { let mut colors = ColorGenerator::new(); colors.next(); @@ -17,7 +18,7 @@ pub fn lowering_error_to_report( program_id, } => { let offset = span.from; - let path = file_paths[program_id].display().to_string(); + let path = session.file_paths[program_id].display().to_string(); Report::build(ReportKind::Error, path.clone(), offset) .with_code("ModuleNotFound") .with_label( @@ -33,7 +34,7 @@ pub fn lowering_error_to_report( function, program_id, } => { - let path = file_paths[program_id].display().to_string(); + let path = session.file_paths[program_id].display().to_string(); Report::build(ReportKind::Error, path.clone(), span.from) .with_code("FunctionNotFound") .with_label( @@ -48,7 +49,7 @@ pub fn lowering_error_to_report( name, program_id, } => { - let path = file_paths[program_id].display().to_string(); + let path = session.file_paths[program_id].display().to_string(); Report::build(ReportKind::Error, path.clone(), span.from) .with_code("StructFieldNotFound") .with_label( @@ -64,7 +65,7 @@ pub fn lowering_error_to_report( symbol, program_id, } => { - let path = file_paths[program_id].display().to_string(); + let path = session.file_paths[program_id].display().to_string(); let offset = symbol.span.from; Report::build(ReportKind::Error, path.clone(), offset) .with_code("ImportNotFound") @@ -90,7 +91,7 @@ pub fn lowering_error_to_report( type_span, program_id, } => { - let path = file_paths[program_id].display().to_string(); + let path = session.file_paths[program_id].display().to_string(); let mut labels = vec![Label::new((path.clone(), span.into())) .with_message(format!( "Can't mutate {name:?} because it's behind a immutable borrow" @@ -115,7 +116,7 @@ pub fn lowering_error_to_report( name, program_id, } => { - let path = file_paths[program_id].display().to_string(); + let path = session.file_paths[program_id].display().to_string(); Report::build(ReportKind::Error, path.clone(), span.from) .with_code("UnrecognizedType") .with_label( @@ -131,7 +132,7 @@ pub fn lowering_error_to_report( id, program_id, } => { - let path = file_paths[program_id].display().to_string(); + let path = session.file_paths[program_id].display().to_string(); Report::build(ReportKind::Error, path.clone(), span.from) .with_code("E_ID") .with_label( @@ -147,7 +148,7 @@ pub fn lowering_error_to_report( message, program_id, } => { - let path = file_paths[program_id].display().to_string(); + let path = session.file_paths[program_id].display().to_string(); Report::build(ReportKind::Error, path.clone(), span.from) .with_code("NotYetImplemented") .with_label( @@ -163,7 +164,7 @@ pub fn lowering_error_to_report( expected, program_id, } => { - let path = file_paths[program_id].display().to_string(); + let path = session.file_paths[program_id].display().to_string(); let mut labels = vec![Label::new((path.clone(), span.into())) .with_message(format!( "Unexpected type '{}', expected '{}'", @@ -190,7 +191,7 @@ pub fn lowering_error_to_report( name, program_id, } => { - let path = file_paths[program_id].display().to_string(); + let path = session.file_paths[program_id].display().to_string(); Report::build(ReportKind::Error, path.clone(), span.from) .with_code("UseOfUndeclaredVariable") .with_label( @@ -205,7 +206,7 @@ pub fn lowering_error_to_report( name, program_id, } => { - let path = file_paths[program_id].display().to_string(); + let path = session.file_paths[program_id].display().to_string(); Report::build(ReportKind::Error, path.clone(), span.from) .with_code("ExternFnWithBody") .with_label( @@ -216,7 +217,7 @@ pub fn lowering_error_to_report( .finish() } LoweringError::InternalError(msg, program_id) => { - let path = file_paths[program_id].display().to_string(); + let path = session.file_paths[program_id].display().to_string(); Report::build(ReportKind::Error, path.clone(), 0) .with_code("InternalError") .with_message(msg) @@ -228,7 +229,7 @@ pub fn lowering_error_to_report( needs, program_id, } => { - let path = file_paths[program_id].display().to_string(); + let path = session.file_paths[program_id].display().to_string(); Report::build(ReportKind::Error, path.clone(), span.from) .with_code("CallParamCountMismatch") .with_label( @@ -247,7 +248,7 @@ pub fn lowering_error_to_report( declare_span, program_id, } => { - let path = file_paths[program_id].display().to_string(); + let path = session.file_paths[program_id].display().to_string(); let mut report = Report::build(ReportKind::Error, path.clone(), span.from) .with_code("NotMutable") .with_label( @@ -270,7 +271,7 @@ pub fn lowering_error_to_report( declare_span, program_id, } => { - let path = file_paths[program_id].display().to_string(); + let path = session.file_paths[program_id].display().to_string(); let mut report = Report::build(ReportKind::Error, path.clone(), span.from) .with_code("CantTakeMutableBorrow") .with_label( diff --git a/crates/concrete_driver/src/lib.rs b/crates/concrete_driver/src/lib.rs index 85c5880..48284ff 100644 --- a/crates/concrete_driver/src/lib.rs +++ b/crates/concrete_driver/src/lib.rs @@ -1,8 +1,10 @@ use anyhow::bail; use anyhow::Context; use anyhow::Result; +use ariadne::Source; use clap::Args; use clap::{Parser, Subcommand}; +use concrete_ast::Program; use concrete_ir::lowering::lower_programs; use concrete_parser::{error::Diagnostics, ProgramSource}; use concrete_session::{ @@ -10,12 +12,12 @@ use concrete_session::{ Session, }; use config::{Package, Profile}; +use db::Database; use git2::{IndexAddOption, Repository}; use owo_colors::OwoColorize; use std::io::Read; use std::os::unix::process::CommandExt; use std::{collections::HashMap, fs::File, path::PathBuf, time::Instant}; -use walkdir::WalkDir; use crate::{ config::Config, @@ -394,7 +396,6 @@ fn handle_build( if !target_dir.exists() { std::fs::create_dir_all(&target_dir)?; } - let has_main = src_dir.join("main.con").exists(); let output = target_dir.join(config.package.name); let (profile, profile_name) = if let Some(profile) = profile { ( @@ -421,26 +422,46 @@ fn handle_build( "dev".to_string(), ) }; - let compile_args = CompilerArgs { - input: src_dir, - output: output.clone(), - release, - optlevel: Some(profile.opt_level), - debug_info: Some(profile.debug_info), - library: !has_main, - ast, - ir, - llvm, - asm, - object, - mlir, - }; + + let lib_ed = src_dir.join("lib.con"); + let main_ed = src_dir.join("main.con"); + let start = Instant::now(); - let object = compile(&compile_args)?; - if !has_main { - link_shared_lib(&[object], &output)?; - } else { - link_binary(&[object], &output)?; + + for file in [main_ed, lib_ed] { + if file.exists() { + let is_lib = file.file_stem().unwrap() == "lib"; + + let compile_args = CompilerArgs { + input: file, + output: if is_lib { + let name = output.file_stem().unwrap().to_string_lossy().to_string(); + let name = format!("lib{name}"); + output + .with_file_name(name) + .with_extension(Session::get_platform_library_ext()) + } else { + output.clone() + }, + release, + optlevel: Some(profile.opt_level), + debug_info: Some(profile.debug_info), + library: is_lib, + ast, + ir, + llvm, + asm, + object, + mlir, + }; + let object = compile(&compile_args)?; + + if compile_args.library { + link_shared_lib(&[object], &compile_args.output)?; + } else { + link_binary(&[object], &compile_args.output)?; + } + } } let elapsed = start.elapsed(); println!( @@ -464,54 +485,56 @@ fn handle_build( } } -pub fn compile(args: &CompilerArgs) -> Result { - let mut files = Vec::new(); - for entry in WalkDir::new(&args.input) { - let entry = entry?; - if let Some(ext) = entry.path().extension() { - if ext.eq_ignore_ascii_case("con") { - files.push(entry.path().to_path_buf()); - } - } +pub fn parse_file( + modules: &mut Vec<(PathBuf, String, Program)>, + mut path: PathBuf, + db: &Database, +) -> Result<()> { + if path.is_dir() { + path = path.join("mod.ed"); } - if files.is_empty() { - panic!("files is empty"); + let real_source = std::fs::read_to_string(&path)?; + let source = ProgramSource::new(db, real_source.clone(), path.display().to_string()); + + let mut program = match concrete_parser::parse_ast(db, source) { + Some(x) => x, + None => { + Diagnostics::dump( + db, + source, + &concrete_parser::parse_ast::accumulated::( + db, source, + ), + ); + std::process::exit(1); + } + }; + program.file_path = Some(path.clone()); + + for ident in program.modules.iter().flat_map(|x| &x.external_modules) { + let module_path = path + .parent() + .unwrap() + .join(&ident.name) + .with_extension("con"); + parse_file(modules, module_path, db)?; } + modules.push((path, real_source, program)); + + Ok(()) +} + +pub fn compile(args: &CompilerArgs) -> Result { let start_time = Instant::now(); let mut programs = Vec::new(); - let db = crate::db::Database::default(); - - let mut sources = Vec::new(); - let mut sources_str = Vec::new(); - - for path in files { - let source = std::fs::read_to_string(&path)?; - let source = ProgramSource::new(&db, source, args.input.display().to_string()); - - let mut program = match concrete_parser::parse_ast(&db, source) { - Some(x) => x, - None => { - Diagnostics::dump( - &db, - source, - &concrete_parser::parse_ast::accumulated::( - &db, source, - ), - ); - std::process::exit(1); - } - }; - program.file_path = Some(path); - sources.push(ariadne::Source::from(source.input(&db).clone())); - sources_str.push(source.input(&db).clone()); - programs.push(program); - } + parse_file(&mut programs, args.input.clone(), &db)?; let session = Session { + file_paths: programs.iter().map(|x| x.0.clone()).collect(), debug_info: if let Some(debug_info) = args.debug_info { if debug_info { DebugInfo::Full @@ -535,7 +558,7 @@ pub fn compile(args: &CompilerArgs) -> Result { } else { OptLevel::None }, - sources, + sources: programs.iter().map(|x| Source::from(x.1.clone())).collect(), library: args.library, output_file: args.output.with_extension("o"), output_asm: args.asm, @@ -549,18 +572,7 @@ pub fn compile(args: &CompilerArgs) -> Result { let path_cache: Vec<_> = programs .iter() - .zip(&sources_str) - .map(|(program, source)| { - ( - program - .file_path - .clone() - .expect("path should always exist here") - .to_string_lossy() - .to_string(), - source.clone(), - ) - }) + .map(|x| (x.0.display().to_string(), x.1.clone())) .collect(); if args.ast { @@ -570,14 +582,11 @@ pub fn compile(args: &CompilerArgs) -> Result { )?; } - let program_ir = match lower_programs(&programs) { + let modules: Vec<_> = programs.iter().map(|x| x.2.clone()).collect(); + let program_ir = match lower_programs(&modules) { Ok(ir) => ir, Err(error) => { - let file_paths: Vec<_> = programs - .iter() - .map(|x| x.file_path.clone().unwrap()) - .collect(); - let report = concrete_check::lowering_error_to_report(error, &file_paths); + let report = concrete_check::lowering_error_to_report(error, &session); report.eprint(ariadne::sources(path_cache))?; std::process::exit(1); } diff --git a/crates/concrete_driver/tests/common.rs b/crates/concrete_driver/tests/common.rs index bdf2e5d..2290e5c 100644 --- a/crates/concrete_driver/tests/common.rs +++ b/crates/concrete_driver/tests/common.rs @@ -61,7 +61,7 @@ pub fn compile_program( let input_file = test_dir_path.join(name).with_extension(".con"); std::fs::write(&input_file, source.input(&db))?; - program.file_path = Some(input_file); + program.file_path = Some(input_file.clone()); let output_file = test_dir_path.join(name); let output_file = if library { @@ -81,6 +81,7 @@ pub fn compile_program( output_mlir: false, output_ll: false, output_asm: false, + file_paths: vec![input_file], }; let program_ir = lower_programs(&[program])?; diff --git a/crates/concrete_parser/src/grammar.lalrpop b/crates/concrete_parser/src/grammar.lalrpop index 7990ba8..821d76d 100644 --- a/crates/concrete_parser/src/grammar.lalrpop +++ b/crates/concrete_parser/src/grammar.lalrpop @@ -207,11 +207,16 @@ pub Program: ast::Program = { // Modules +pub(crate) ExternalModule: ast::common::Ident = { + "mod" ";" => name +} + pub(crate) Module: ast::modules::Module = { - "mod" "{" "}" => { + "mod" "{" ?> "}" => { ast::modules::Module { doc_string: None, imports: imports.unwrap_or_else(Vec::new), + external_modules: external_modules.unwrap_or(vec![]), name, contents: contents.unwrap_or_else(Vec::new), span: Span::new(lo, hi), diff --git a/crates/concrete_session/src/lib.rs b/crates/concrete_session/src/lib.rs index c316f4a..299c17e 100644 --- a/crates/concrete_session/src/lib.rs +++ b/crates/concrete_session/src/lib.rs @@ -5,16 +5,29 @@ use config::{DebugInfo, OptLevel}; pub mod config; +/// This struct holds the information needed to compile this compilation unit, +/// like whether to generate debug info, optimization levels, target, host, etc. #[derive(Debug, Clone)] pub struct Session { + /// The file paths of the included sources from the initial compile unit. + pub file_paths: Vec, + /// Whether to output debug info. pub debug_info: DebugInfo, + /// The optimization level to use with this compilation unit. pub optlevel: OptLevel, - pub sources: Vec, // for debugging locations + /// Sources for debugging locations. + pub sources: Vec>, /// True if it should be compiled as a library false for binary. pub library: bool, + /// The file where to put the compilation result. + /// The file name will be used for all the other options, if it's + /// a library, the platform extension will be added. pub output_file: PathBuf, + /// Whether to output the generated MLIR file for this compile unit. pub output_mlir: bool, + /// Whether to output the generated LLVM IR file for this compile unit. pub output_ll: bool, + /// Whether to output the generated assembly file for this compile unit. pub output_asm: bool, // todo: include target, host, etc }