From 52f84910a764b0ca63b97bdabccc79886a10da81 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Fri, 26 Apr 2024 23:33:14 -0300 Subject: [PATCH] Support importing local files in the REPL --- crates/compiler/parse/src/header.rs | 6 ++++ crates/repl_cli/src/lib.rs | 5 ++- crates/repl_eval/src/gen.rs | 2 +- crates/repl_ui/src/repl_state.rs | 51 ++++++++++++++++++++++++----- crates/repl_wasm/src/repl.rs | 3 ++ 5 files changed, 57 insertions(+), 10 deletions(-) diff --git a/crates/compiler/parse/src/header.rs b/crates/compiler/parse/src/header.rs index 9e13c50f55..b13ff1b366 100644 --- a/crates/compiler/parse/src/header.rs +++ b/crates/compiler/parse/src/header.rs @@ -163,6 +163,8 @@ impl<'a> From> for roc_module::ident::ModuleName { } impl<'a> ModuleName<'a> { + const MODULE_SEPARATOR: char = '.'; + pub const fn new(name: &'a str) -> Self { ModuleName(name) } @@ -170,6 +172,10 @@ impl<'a> ModuleName<'a> { pub const fn as_str(&'a self) -> &'a str { self.0 } + + pub fn parts(&'a self) -> impl DoubleEndedIterator { + self.0.split(Self::MODULE_SEPARATOR) + } } #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] diff --git a/crates/repl_cli/src/lib.rs b/crates/repl_cli/src/lib.rs index a340a13369..08ee592498 100644 --- a/crates/repl_cli/src/lib.rs +++ b/crates/repl_cli/src/lib.rs @@ -9,7 +9,7 @@ use roc_repl_eval::gen::Problems; use roc_repl_ui::colors::{CYAN, END_COL}; use roc_repl_ui::repl_state::{ReplAction, ReplState}; use roc_repl_ui::{format_output, is_incomplete, CONT_PROMPT, PROMPT, SHORT_INSTRUCTIONS, TIPS}; -use roc_reporting::report::{ANSI_STYLE_CODES, DEFAULT_PALETTE}; +use roc_reporting::report::{to_file_problem_report_string, ANSI_STYLE_CODES, DEFAULT_PALETTE}; use roc_target::Target; use rustyline::highlight::{Highlighter, PromptInfo}; use rustyline::validate::{self, ValidationContext, ValidationResult, Validator}; @@ -74,6 +74,9 @@ pub fn main() -> i32 { ReplAction::Exit => { return 0; } + ReplAction::FileProblem { filename, error } => { + println!("{}", to_file_problem_report_string(filename, error)); + } ReplAction::Help => { println!("{TIPS}"); } diff --git a/crates/repl_eval/src/gen.rs b/crates/repl_eval/src/gen.rs index 8fbf00fe92..59c21e48e0 100644 --- a/crates/repl_eval/src/gen.rs +++ b/crates/repl_eval/src/gen.rs @@ -53,7 +53,7 @@ pub fn compile_to_mono<'a, 'i, I: Iterator>( palette: Palette, ) -> (Option>, Problems) { let filename = PathBuf::from("replfile.roc"); - let src_dir = PathBuf::from("fake/test/path"); + let src_dir = PathBuf::from("."); let (bytes_before_expr, module_src) = promote_expr_to_module(arena, defs, expr); let loaded = roc_load::load_and_monomorphize_from_str( arena, diff --git a/crates/repl_ui/src/repl_state.rs b/crates/repl_ui/src/repl_state.rs index 4069a68962..73041ec078 100644 --- a/crates/repl_ui/src/repl_state.rs +++ b/crates/repl_ui/src/repl_state.rs @@ -1,3 +1,6 @@ +use std::path::PathBuf; +use std::{fs, io}; + use bumpalo::Bump; use roc_collections::MutSet; use roc_load::MonomorphizedModule; @@ -14,9 +17,9 @@ use roc_reporting::report::Palette; use roc_target::Target; #[derive(Debug, Clone, PartialEq)] -struct PastDef { - ident: String, - src: String, +enum PastDef { + Def { ident: String, src: String }, + Import(String), } pub struct ReplState { @@ -39,6 +42,10 @@ pub enum ReplAction<'a> { }, Exit, Help, + FileProblem { + filename: PathBuf, + error: io::ErrorKind, + }, Nothing, } @@ -134,9 +141,34 @@ impl ReplState { ValueDef::ExpectFx { .. } => { todo!("handle receiving an `expect-fx` - what should the repl do for that?") } - ValueDef::ModuleImport(_) => { - todo!("handle importing a module from the REPL") - } + ValueDef::ModuleImport(import) => match import.name.value.package { + Some(_) => { + todo!("handle importing a module from a package") + } + None => { + let mut filename = PathBuf::new(); + + for part in import.name.value.name.parts() { + filename.push(part); + } + + filename.set_extension("roc"); + + // Check we can read the file before we add it to past defs. + // If we didn't do this, the bad import would remain in past_defs + // and we'd report it on every subsequent evaluation. + if let Err(err) = fs::metadata(&filename) { + return ReplAction::FileProblem { + filename, + error: err.kind(), + }; + } + + self.past_defs.push(PastDef::Import(line.to_string())); + + return ReplAction::Nothing; + } + }, ValueDef::IngestedFileImport(_) => { todo!("handle ingesting a file from the REPL") } @@ -178,7 +210,10 @@ impl ReplState { let (opt_mono, problems) = compile_to_mono( arena, - self.past_defs.iter().map(|def| def.src.as_str()), + self.past_defs.iter().map(|past_def| match past_def { + PastDef::Def { ident: _, src } => src.as_str(), + PastDef::Import(src) => src.as_str(), + }), src, target, palette, @@ -196,7 +231,7 @@ impl ReplState { existing_idents.insert(ident.clone()); - self.past_defs.push(PastDef { ident, src }); + self.past_defs.push(PastDef::Def { ident, src }); } } diff --git a/crates/repl_wasm/src/repl.rs b/crates/repl_wasm/src/repl.rs index 8614ee4b58..d010e40aa5 100644 --- a/crates/repl_wasm/src/repl.rs +++ b/crates/repl_wasm/src/repl.rs @@ -203,6 +203,9 @@ pub async fn entrypoint_from_js(src: String) -> String { ReplAction::Exit => { "To exit the web version of the REPL, just close the browser tab!".to_string() } + ReplAction::FileProblem { .. } => { + "The web version of the REPL cannot import files... for now!".to_string() + } ReplAction::Nothing => String::new(), ReplAction::Eval { opt_mono, problems } => { let opt_output = match opt_mono {