mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-24 15:03:46 +00:00
merge upstream/main
This commit is contained in:
commit
cec67721e6
59 changed files with 2542 additions and 990 deletions
1
.github/workflows/markdown_link_check.yml
vendored
1
.github/workflows/markdown_link_check.yml
vendored
|
@ -18,6 +18,7 @@ jobs:
|
|||
with:
|
||||
use-quiet-mode: 'yes'
|
||||
use-verbose-mode: 'yes'
|
||||
check-modified-files-only: 'yes'
|
||||
base-branch: 'main'
|
||||
check-modified-files-only: 'yes'
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
|
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -3234,6 +3234,7 @@ version = "0.0.1"
|
|||
dependencies = [
|
||||
"indoc",
|
||||
"lazy_static",
|
||||
"roc_build",
|
||||
"roc_cli",
|
||||
"roc_repl_cli",
|
||||
"roc_test_utils",
|
||||
|
@ -3412,6 +3413,7 @@ dependencies = [
|
|||
"roc_fmt",
|
||||
"roc_gen_llvm",
|
||||
"roc_glue",
|
||||
"roc_intern",
|
||||
"roc_linker",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
|
@ -3921,6 +3923,7 @@ dependencies = [
|
|||
"rustyline",
|
||||
"rustyline-derive",
|
||||
"target-lexicon",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3958,6 +3961,7 @@ dependencies = [
|
|||
"roc_builtins",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_gen_llvm",
|
||||
"roc_intern",
|
||||
"roc_load",
|
||||
|
@ -5165,9 +5169,9 @@ checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
|
|||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.9.0"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
|
||||
checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
|
|
|
@ -60,6 +60,7 @@ roc_editor = { path = "../editor", optional = true }
|
|||
roc_linker = { path = "../linker" }
|
||||
roc_repl_cli = { path = "../repl_cli", optional = true }
|
||||
roc_tracing = { path = "../tracing" }
|
||||
roc_intern = { path = "../compiler/intern" }
|
||||
clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions"] }
|
||||
const_format = { version = "0.2.23", features = ["const_generics"] }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
use bumpalo::Bump;
|
||||
use roc_build::{
|
||||
link::{link, preprocess_host_wasm32, rebuild_host, LinkType, LinkingStrategy},
|
||||
program::{self, Problems},
|
||||
program::{self, CodeGenOptions, Problems},
|
||||
};
|
||||
use roc_builtins::bitcode;
|
||||
use roc_collections::VecMap;
|
||||
use roc_load::{
|
||||
EntryPoint, ExecutionMode, Expectations, LoadConfig, LoadMonomorphizedError, LoadedModule,
|
||||
EntryPoint, ExecutionMode, ExpectMetadata, LoadConfig, LoadMonomorphizedError, LoadedModule,
|
||||
LoadingProblem, Threading,
|
||||
};
|
||||
use roc_module::symbol::{Interns, ModuleId};
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_reporting::report::RenderTarget;
|
||||
use roc_target::TargetInfo;
|
||||
|
@ -30,12 +28,11 @@ fn report_timing(buf: &mut String, label: &str, duration: Duration) {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
pub struct BuiltFile {
|
||||
pub struct BuiltFile<'a> {
|
||||
pub binary_path: PathBuf,
|
||||
pub problems: Problems,
|
||||
pub total_time: Duration,
|
||||
pub expectations: VecMap<ModuleId, Expectations>,
|
||||
pub interns: Interns,
|
||||
pub expect_metadata: ExpectMetadata<'a>,
|
||||
}
|
||||
|
||||
pub enum BuildOrdering {
|
||||
|
@ -60,8 +57,7 @@ pub fn build_file<'a>(
|
|||
arena: &'a Bump,
|
||||
target: &Triple,
|
||||
app_module_path: PathBuf,
|
||||
opt_level: OptLevel,
|
||||
emit_debug_info: bool,
|
||||
code_gen_options: CodeGenOptions,
|
||||
emit_timings: bool,
|
||||
link_type: LinkType,
|
||||
linking_strategy: LinkingStrategy,
|
||||
|
@ -69,7 +65,7 @@ pub fn build_file<'a>(
|
|||
threading: Threading,
|
||||
wasm_dev_stack_bytes: Option<u32>,
|
||||
order: BuildOrdering,
|
||||
) -> Result<BuiltFile, BuildFileError<'a>> {
|
||||
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
|
||||
let compilation_start = Instant::now();
|
||||
let target_info = TargetInfo::from(target);
|
||||
|
||||
|
@ -121,7 +117,7 @@ pub fn build_file<'a>(
|
|||
|
||||
match roc_target::OperatingSystem::from(target.operating_system) {
|
||||
Wasi => {
|
||||
if matches!(opt_level, OptLevel::Development) {
|
||||
if matches!(code_gen_options.opt_level, OptLevel::Development) {
|
||||
("wasm", "wasm", Some("wasm"))
|
||||
} else {
|
||||
("zig", "bc", Some("wasm"))
|
||||
|
@ -180,7 +176,7 @@ pub fn build_file<'a>(
|
|||
};
|
||||
|
||||
let rebuild_thread = spawn_rebuild_thread(
|
||||
opt_level,
|
||||
code_gen_options.opt_level,
|
||||
linking_strategy,
|
||||
prebuilt,
|
||||
host_input_path.clone(),
|
||||
|
@ -241,11 +237,8 @@ pub fn build_file<'a>(
|
|||
// inside a nested scope without causing a borrow error!
|
||||
let mut loaded = loaded;
|
||||
let problems = program::report_problems_monomorphized(&mut loaded);
|
||||
let expectations = std::mem::take(&mut loaded.expectations);
|
||||
let loaded = loaded;
|
||||
|
||||
let interns = loaded.interns.clone();
|
||||
|
||||
enum HostRebuildTiming {
|
||||
BeforeApp(u128),
|
||||
ConcurrentWithApp(JoinHandle<u128>),
|
||||
|
@ -266,13 +259,12 @@ pub fn build_file<'a>(
|
|||
HostRebuildTiming::ConcurrentWithApp(rebuild_thread)
|
||||
};
|
||||
|
||||
let (roc_app_bytes, code_gen_timing) = program::gen_from_mono_module(
|
||||
let (roc_app_bytes, code_gen_timing, expect_metadata) = program::gen_from_mono_module(
|
||||
arena,
|
||||
loaded,
|
||||
&app_module_path,
|
||||
target,
|
||||
opt_level,
|
||||
emit_debug_info,
|
||||
code_gen_options,
|
||||
&preprocessed_host_path,
|
||||
wasm_dev_stack_bytes,
|
||||
);
|
||||
|
@ -351,7 +343,7 @@ pub fn build_file<'a>(
|
|||
|
||||
let str_host_obj_path = bitcode::get_builtins_host_obj_path();
|
||||
|
||||
if matches!(opt_level, OptLevel::Development) {
|
||||
if matches!(code_gen_options.backend, program::CodeGenBackend::Assembly) {
|
||||
inputs.push(&str_host_obj_path);
|
||||
}
|
||||
|
||||
|
@ -388,8 +380,7 @@ pub fn build_file<'a>(
|
|||
binary_path,
|
||||
problems,
|
||||
total_time,
|
||||
interns,
|
||||
expectations,
|
||||
expect_metadata,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -7,11 +7,9 @@ use build::BuiltFile;
|
|||
use bumpalo::Bump;
|
||||
use clap::{Arg, ArgMatches, Command, ValueSource};
|
||||
use roc_build::link::{LinkType, LinkingStrategy};
|
||||
use roc_build::program::Problems;
|
||||
use roc_collections::VecMap;
|
||||
use roc_build::program::{CodeGenBackend, CodeGenOptions, Problems};
|
||||
use roc_error_macros::{internal_error, user_error};
|
||||
use roc_load::{Expectations, LoadingProblem, Threading};
|
||||
use roc_module::symbol::{Interns, ModuleId};
|
||||
use roc_load::{ExpectMetadata, LoadingProblem, Threading};
|
||||
use roc_mono::ir::OptLevel;
|
||||
use std::env;
|
||||
use std::ffi::{CString, OsStr};
|
||||
|
@ -432,7 +430,7 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
|
|||
|
||||
let mut writer = std::io::stdout();
|
||||
|
||||
let (failed, passed) = roc_repl_expect::run::run_expects(
|
||||
let (failed, passed) = roc_repl_expect::run::run_toplevel_expects(
|
||||
&mut writer,
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
arena,
|
||||
|
@ -481,18 +479,33 @@ pub fn build(
|
|||
use build::build_file;
|
||||
use BuildConfig::*;
|
||||
|
||||
let arena = Bump::new();
|
||||
let filename = matches.value_of_os(ROC_FILE).unwrap();
|
||||
let opt_level = match (
|
||||
// the process will end after this function,
|
||||
// so we don't want to spend time freeing these values
|
||||
let arena = ManuallyDrop::new(Bump::new());
|
||||
|
||||
let code_gen_backend = if matches!(triple.architecture, Architecture::Wasm32) {
|
||||
CodeGenBackend::Wasm
|
||||
} else {
|
||||
match matches.is_present(FLAG_DEV) {
|
||||
true => CodeGenBackend::Assembly,
|
||||
false => CodeGenBackend::Llvm,
|
||||
}
|
||||
};
|
||||
|
||||
let opt_level = if let BuildConfig::BuildAndRunIfNoErrors = config {
|
||||
OptLevel::Development
|
||||
} else {
|
||||
match (
|
||||
matches.is_present(FLAG_OPTIMIZE),
|
||||
matches.is_present(FLAG_OPT_SIZE),
|
||||
matches.is_present(FLAG_DEV),
|
||||
) {
|
||||
(true, false, false) => OptLevel::Optimize,
|
||||
(false, true, false) => OptLevel::Size,
|
||||
(false, false, true) => OptLevel::Development,
|
||||
(false, false, false) => OptLevel::Normal,
|
||||
_ => user_error!("build can be only one of `--dev`, `--optimize`, or `--opt-size`"),
|
||||
(true, false) => OptLevel::Optimize,
|
||||
(false, true) => OptLevel::Size,
|
||||
(false, false) => OptLevel::Normal,
|
||||
(true, true) => {
|
||||
user_error!("build can be only one of `--optimize` and `--opt-size`")
|
||||
}
|
||||
}
|
||||
};
|
||||
let emit_debug_info = matches.is_present(FLAG_DEBUG);
|
||||
let emit_timings = matches.is_present(FLAG_TIME);
|
||||
|
@ -508,7 +521,7 @@ pub fn build(
|
|||
};
|
||||
|
||||
let wasm_dev_backend = matches!(opt_level, OptLevel::Development)
|
||||
&& matches!(triple.architecture, Architecture::Wasm32);
|
||||
&& matches!(code_gen_backend, CodeGenBackend::Wasm);
|
||||
|
||||
let linking_strategy = if wasm_dev_backend {
|
||||
LinkingStrategy::Additive
|
||||
|
@ -528,6 +541,8 @@ pub fn build(
|
|||
// We make an exception for Wasm, because cross-compiling is the norm in that case.
|
||||
triple != Triple::host() && !matches!(triple.architecture, Architecture::Wasm32)
|
||||
};
|
||||
|
||||
let filename = matches.value_of_os(ROC_FILE).unwrap();
|
||||
let path = Path::new(filename);
|
||||
|
||||
// Spawn the root task
|
||||
|
@ -559,12 +574,18 @@ pub fn build(
|
|||
BuildAndRunIfNoErrors => BuildOrdering::BuildIfChecks,
|
||||
_ => BuildOrdering::AlwaysBuild,
|
||||
};
|
||||
|
||||
let code_gen_options = CodeGenOptions {
|
||||
backend: code_gen_backend,
|
||||
opt_level,
|
||||
emit_debug_info,
|
||||
};
|
||||
|
||||
let res_binary_path = build_file(
|
||||
&arena,
|
||||
&triple,
|
||||
path.to_path_buf(),
|
||||
opt_level,
|
||||
emit_debug_info,
|
||||
code_gen_options,
|
||||
emit_timings,
|
||||
link_type,
|
||||
linking_strategy,
|
||||
|
@ -579,8 +600,7 @@ pub fn build(
|
|||
binary_path,
|
||||
problems,
|
||||
total_time,
|
||||
expectations,
|
||||
interns,
|
||||
expect_metadata,
|
||||
}) => {
|
||||
match config {
|
||||
BuildOnly => {
|
||||
|
@ -593,7 +613,7 @@ pub fn build(
|
|||
|
||||
// No need to waste time freeing this memory,
|
||||
// since the process is about to exit anyway.
|
||||
std::mem::forget(arena);
|
||||
// std::mem::forget(arena);
|
||||
|
||||
print_problems(problems, total_time);
|
||||
println!(" while successfully building:\n\n {generated_filename}");
|
||||
|
@ -616,7 +636,7 @@ pub fn build(
|
|||
// ManuallyDrop will leak the bytes because we don't drop manually
|
||||
let bytes = &ManuallyDrop::new(std::fs::read(&binary_path).unwrap());
|
||||
|
||||
roc_run(arena, opt_level, triple, args, bytes, expectations, interns)
|
||||
roc_run(&arena, opt_level, triple, args, bytes, expect_metadata)
|
||||
}
|
||||
BuildAndRunIfNoErrors => {
|
||||
debug_assert!(
|
||||
|
@ -637,7 +657,7 @@ pub fn build(
|
|||
// ManuallyDrop will leak the bytes because we don't drop manually
|
||||
let bytes = &ManuallyDrop::new(std::fs::read(&binary_path).unwrap());
|
||||
|
||||
roc_run(arena, opt_level, triple, args, bytes, expectations, interns)
|
||||
roc_run(&arena, opt_level, triple, args, bytes, expect_metadata)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -703,13 +723,12 @@ fn print_problems(problems: Problems, total_time: std::time::Duration) {
|
|||
}
|
||||
|
||||
fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
|
||||
arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
|
||||
arena: &Bump,
|
||||
opt_level: OptLevel,
|
||||
triple: Triple,
|
||||
args: I,
|
||||
binary_bytes: &[u8],
|
||||
expectations: VecMap<ModuleId, Expectations>,
|
||||
interns: Interns,
|
||||
expect_metadata: ExpectMetadata,
|
||||
) -> io::Result<i32> {
|
||||
match triple.architecture {
|
||||
Architecture::Wasm32 => {
|
||||
|
@ -720,10 +739,6 @@ fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
|
|||
.strip_prefix(env::current_dir().unwrap())
|
||||
.unwrap_or(path);
|
||||
|
||||
// No need to waste time freeing this memory,
|
||||
// since the process is about to exit anyway.
|
||||
std::mem::forget(arena);
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
@ -748,7 +763,7 @@ fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
|
|||
|
||||
Ok(0)
|
||||
}
|
||||
_ => roc_run_native(arena, opt_level, args, binary_bytes, expectations, interns),
|
||||
_ => roc_run_native(arena, opt_level, args, binary_bytes, expect_metadata),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -848,35 +863,32 @@ fn make_argv_envp_windows<'a, I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
|
|||
/// Run on the native OS (not on wasm)
|
||||
#[cfg(target_family = "unix")]
|
||||
fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
|
||||
arena: Bump,
|
||||
arena: &Bump,
|
||||
opt_level: OptLevel,
|
||||
args: I,
|
||||
binary_bytes: &[u8],
|
||||
expectations: VecMap<ModuleId, Expectations>,
|
||||
interns: Interns,
|
||||
expect_metadata: ExpectMetadata,
|
||||
) -> std::io::Result<i32> {
|
||||
use bumpalo::collections::CollectIn;
|
||||
|
||||
unsafe {
|
||||
let executable = roc_run_executable_file_path(binary_bytes)?;
|
||||
let (argv_cstrings, envp_cstrings) = make_argv_envp(&arena, &executable, args);
|
||||
let (argv_cstrings, envp_cstrings) = make_argv_envp(arena, &executable, args);
|
||||
|
||||
let argv: bumpalo::collections::Vec<*const c_char> = argv_cstrings
|
||||
.iter()
|
||||
.map(|s| s.as_ptr())
|
||||
.chain([std::ptr::null()])
|
||||
.collect_in(&arena);
|
||||
.collect_in(arena);
|
||||
|
||||
let envp: bumpalo::collections::Vec<*const c_char> = envp_cstrings
|
||||
.iter()
|
||||
.map(|s| s.as_ptr())
|
||||
.chain([std::ptr::null()])
|
||||
.collect_in(&arena);
|
||||
.collect_in(arena);
|
||||
|
||||
match opt_level {
|
||||
OptLevel::Development => {
|
||||
roc_run_native_debug(executable, &argv, &envp, expectations, interns)
|
||||
}
|
||||
OptLevel::Development => roc_dev_native(arena, executable, argv, envp, expect_metadata),
|
||||
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => {
|
||||
roc_run_native_fast(executable, &argv, &envp);
|
||||
}
|
||||
|
@ -950,14 +962,76 @@ impl ExecutableFile {
|
|||
|
||||
// with Expect
|
||||
#[cfg(target_family = "unix")]
|
||||
unsafe fn roc_run_native_debug(
|
||||
_executable: ExecutableFile,
|
||||
_argv: &[*const c_char],
|
||||
_envp: &[*const c_char],
|
||||
_expectations: VecMap<ModuleId, Expectations>,
|
||||
_interns: Interns,
|
||||
) {
|
||||
todo!()
|
||||
fn roc_dev_native(
|
||||
arena: &Bump,
|
||||
executable: ExecutableFile,
|
||||
argv: bumpalo::collections::Vec<*const c_char>,
|
||||
envp: bumpalo::collections::Vec<*const c_char>,
|
||||
expect_metadata: ExpectMetadata,
|
||||
) -> ! {
|
||||
use roc_repl_expect::run::ExpectMemory;
|
||||
use signal_hook::{consts::signal::SIGCHLD, consts::signal::SIGUSR1, iterator::Signals};
|
||||
|
||||
let ExpectMetadata {
|
||||
mut expectations,
|
||||
interns,
|
||||
layout_interner,
|
||||
} = expect_metadata;
|
||||
|
||||
let mut signals = Signals::new(&[SIGCHLD, SIGUSR1]).unwrap();
|
||||
|
||||
// let shm_name =
|
||||
let shm_name = format!("/roc_expect_buffer_{}", std::process::id());
|
||||
let memory = ExpectMemory::create_or_reuse_mmap(&shm_name);
|
||||
|
||||
let layout_interner = layout_interner.into_global();
|
||||
|
||||
let mut writer = std::io::stdout();
|
||||
|
||||
match unsafe { libc::fork() } {
|
||||
0 => unsafe {
|
||||
// we are the child
|
||||
|
||||
executable.execve(&argv, &envp);
|
||||
|
||||
// Display a human-friendly error message
|
||||
println!("Error {:?}", std::io::Error::last_os_error());
|
||||
|
||||
std::process::exit(1);
|
||||
},
|
||||
-1 => {
|
||||
// something failed
|
||||
|
||||
// Display a human-friendly error message
|
||||
println!("Error {:?}", std::io::Error::last_os_error());
|
||||
|
||||
std::process::exit(1)
|
||||
}
|
||||
1.. => {
|
||||
for sig in &mut signals {
|
||||
match sig {
|
||||
SIGCHLD => break,
|
||||
SIGUSR1 => {
|
||||
// this is the signal we use for an expect failure. Let's see what the child told us
|
||||
|
||||
roc_repl_expect::run::render_expects_in_memory(
|
||||
&mut writer,
|
||||
arena,
|
||||
&mut expectations,
|
||||
&interns,
|
||||
&layout_interner,
|
||||
&memory,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
_ => println!("received signal {}", sig),
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
|
@ -1036,12 +1110,11 @@ fn roc_run_executable_file_path(binary_bytes: &[u8]) -> std::io::Result<Executab
|
|||
/// Run on the native OS (not on wasm)
|
||||
#[cfg(not(target_family = "unix"))]
|
||||
fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
|
||||
arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
|
||||
arena: &Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
|
||||
opt_level: OptLevel,
|
||||
args: I,
|
||||
binary_bytes: &[u8],
|
||||
_expectations: VecMap<ModuleId, Expectations>,
|
||||
_interns: Interns,
|
||||
_expect_metadata: ExpectMetadata,
|
||||
) -> io::Result<i32> {
|
||||
use bumpalo::collections::CollectIn;
|
||||
|
||||
|
@ -1055,18 +1128,18 @@ fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
|
|||
.iter()
|
||||
.map(|s| s.as_ptr())
|
||||
.chain([std::ptr::null()])
|
||||
.collect_in(&arena);
|
||||
.collect_in(arena);
|
||||
|
||||
let envp: bumpalo::collections::Vec<*const c_char> = envp_cstrings
|
||||
.iter()
|
||||
.map(|s| s.as_ptr())
|
||||
.chain([std::ptr::null()])
|
||||
.collect_in(&arena);
|
||||
.collect_in(arena);
|
||||
|
||||
match opt_level {
|
||||
OptLevel::Development => {
|
||||
// roc_run_native_debug(executable, &argv, &envp, expectations, interns)
|
||||
todo!()
|
||||
internal_error!("running `expect`s does not currently work on windows")
|
||||
}
|
||||
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => {
|
||||
roc_run_native_fast(executable, &argv, &envp);
|
||||
|
|
|
@ -178,14 +178,7 @@ fn main() -> io::Result<()> {
|
|||
}
|
||||
}
|
||||
}
|
||||
Some((CMD_REPL, _)) => {
|
||||
{
|
||||
roc_repl_cli::main()?;
|
||||
|
||||
// Exit 0 if the repl exited normally
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
Some((CMD_REPL, _)) => Ok(roc_repl_cli::main()),
|
||||
Some((CMD_EDIT, matches)) => {
|
||||
match matches
|
||||
.values_of_os(DIRECTORY_OR_FILES)
|
||||
|
|
|
@ -65,6 +65,34 @@ export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void {
|
|||
std.process.exit(0);
|
||||
}
|
||||
|
||||
extern fn kill(pid: c_int, sig: c_int) c_int;
|
||||
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
|
||||
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
|
||||
extern fn getppid() c_int;
|
||||
|
||||
fn roc_getppid() callconv(.C) c_int {
|
||||
return getppid();
|
||||
}
|
||||
|
||||
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
|
||||
return kill(pid, sig);
|
||||
}
|
||||
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
|
||||
return shm_open(name, oflag, mode);
|
||||
}
|
||||
fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
|
||||
return mmap(addr, length, prot, flags, fd, offset);
|
||||
}
|
||||
|
||||
comptime {
|
||||
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
|
||||
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
|
||||
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
|
||||
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
|
||||
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
|
||||
}
|
||||
}
|
||||
|
||||
const Unit = extern struct {};
|
||||
|
||||
pub export fn main() i32 {
|
||||
|
|
|
@ -64,6 +64,34 @@ export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void {
|
|||
std.process.exit(0);
|
||||
}
|
||||
|
||||
extern fn kill(pid: c_int, sig: c_int) c_int;
|
||||
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
|
||||
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
|
||||
extern fn getppid() c_int;
|
||||
|
||||
fn roc_getppid() callconv(.C) c_int {
|
||||
return getppid();
|
||||
}
|
||||
|
||||
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
|
||||
return kill(pid, sig);
|
||||
}
|
||||
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
|
||||
return shm_open(name, oflag, mode);
|
||||
}
|
||||
fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
|
||||
return mmap(addr, length, prot, flags, fd, offset);
|
||||
}
|
||||
|
||||
comptime {
|
||||
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
|
||||
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
|
||||
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
|
||||
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
|
||||
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
|
||||
}
|
||||
}
|
||||
|
||||
const Unit = extern struct {};
|
||||
|
||||
pub export fn main() i32 {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const testing = std.testing;
|
||||
const expectEqual = testing.expectEqual;
|
||||
const expect = testing.expect;
|
||||
|
@ -13,7 +14,6 @@ comptime {
|
|||
// -fcompiler-rt in link.rs instead of doing this. Note that this
|
||||
// workaround is present in many host.zig files, so make sure to undo
|
||||
// it everywhere!
|
||||
const builtin = @import("builtin");
|
||||
if (builtin.os.tag == .macos) {
|
||||
_ = @import("compiler_rt");
|
||||
}
|
||||
|
@ -81,6 +81,34 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
|
|||
return memset(dst, value, size);
|
||||
}
|
||||
|
||||
extern fn kill(pid: c_int, sig: c_int) c_int;
|
||||
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
|
||||
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
|
||||
extern fn getppid() c_int;
|
||||
|
||||
fn roc_getppid() callconv(.C) c_int {
|
||||
return getppid();
|
||||
}
|
||||
|
||||
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
|
||||
return kill(pid, sig);
|
||||
}
|
||||
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
|
||||
return shm_open(name, oflag, mode);
|
||||
}
|
||||
fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
|
||||
return mmap(addr, length, prot, flags, fd, offset);
|
||||
}
|
||||
|
||||
comptime {
|
||||
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
|
||||
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
|
||||
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
|
||||
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
|
||||
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
|
||||
}
|
||||
}
|
||||
|
||||
pub export fn main() u8 {
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
|
||||
|
|
|
@ -80,6 +80,34 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
|
|||
return memset(dst, value, size);
|
||||
}
|
||||
|
||||
extern fn kill(pid: c_int, sig: c_int) c_int;
|
||||
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
|
||||
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
|
||||
extern fn getppid() c_int;
|
||||
|
||||
fn roc_getppid() callconv(.C) c_int {
|
||||
return getppid();
|
||||
}
|
||||
|
||||
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
|
||||
return kill(pid, sig);
|
||||
}
|
||||
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
|
||||
return shm_open(name, oflag, mode);
|
||||
}
|
||||
fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
|
||||
return mmap(addr, length, prot, flags, fd, offset);
|
||||
}
|
||||
|
||||
comptime {
|
||||
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
|
||||
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
|
||||
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
|
||||
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
|
||||
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
|
||||
}
|
||||
}
|
||||
|
||||
// warning! the array is currently stack-allocated so don't make this too big
|
||||
const NUM_NUMS = 100;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const std = @import("std");
|
||||
const str = @import("str");
|
||||
const builtin = @import("builtin");
|
||||
const str = @import("str");
|
||||
const RocStr = str.RocStr;
|
||||
const testing = std.testing;
|
||||
const expectEqual = testing.expectEqual;
|
||||
|
@ -85,9 +85,39 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
|
|||
return memset(dst, value, size);
|
||||
}
|
||||
|
||||
extern fn kill(pid: c_int, sig: c_int) c_int;
|
||||
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
|
||||
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
|
||||
extern fn getppid() c_int;
|
||||
|
||||
fn roc_getppid() callconv(.C) c_int {
|
||||
return getppid();
|
||||
}
|
||||
|
||||
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
|
||||
return kill(pid, sig);
|
||||
}
|
||||
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
|
||||
return shm_open(name, oflag, mode);
|
||||
}
|
||||
fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
|
||||
return mmap(addr, length, prot, flags, fd, offset);
|
||||
}
|
||||
|
||||
comptime {
|
||||
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
|
||||
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
|
||||
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
|
||||
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
|
||||
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
|
||||
}
|
||||
}
|
||||
|
||||
const Unit = extern struct {};
|
||||
|
||||
pub export fn main() callconv(.C) u8 {
|
||||
pub fn main() !u8 {
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
|
||||
// The size might be zero; if so, make it at least 8 so that we don't have a nullptr
|
||||
const size = std.math.max(@intCast(usize, roc__mainForHost_size()), 8);
|
||||
const raw_output = roc_alloc(@intCast(usize, size), @alignOf(u64)).?;
|
||||
|
@ -108,7 +138,6 @@ pub export fn main() callconv(.C) u8 {
|
|||
const nanos = timer.read();
|
||||
const seconds = (@intToFloat(f64, nanos) / 1_000_000_000.0);
|
||||
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable;
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -3,7 +3,7 @@ use roc_error_macros::internal_error;
|
|||
pub use roc_gen_llvm::llvm::build::FunctionIterator;
|
||||
use roc_gen_llvm::llvm::build::{module_from_builtins, LlvmBackendMode};
|
||||
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
||||
use roc_load::{EntryPoint, LoadedModule, MonomorphizedModule};
|
||||
use roc_load::{EntryPoint, ExpectMetadata, LoadedModule, MonomorphizedModule};
|
||||
use roc_module::symbol::{Interns, ModuleId};
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_region::all::LineInfo;
|
||||
|
@ -174,47 +174,60 @@ impl Deref for CodeObject {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum CodeGenBackend {
|
||||
Assembly,
|
||||
Llvm,
|
||||
Wasm,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CodeGenOptions {
|
||||
pub backend: CodeGenBackend,
|
||||
pub opt_level: OptLevel,
|
||||
pub emit_debug_info: bool,
|
||||
}
|
||||
|
||||
type GenFromMono<'a> = (CodeObject, CodeGenTiming, ExpectMetadata<'a>);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn gen_from_mono_module(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
pub fn gen_from_mono_module<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
loaded: MonomorphizedModule<'a>,
|
||||
roc_file_path: &Path,
|
||||
target: &target_lexicon::Triple,
|
||||
opt_level: OptLevel,
|
||||
emit_debug_info: bool,
|
||||
code_gen_options: CodeGenOptions,
|
||||
preprocessed_host_path: &Path,
|
||||
wasm_dev_stack_bytes: Option<u32>,
|
||||
) -> (CodeObject, CodeGenTiming) {
|
||||
match opt_level {
|
||||
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => gen_from_mono_module_llvm(
|
||||
arena,
|
||||
loaded,
|
||||
roc_file_path,
|
||||
target,
|
||||
opt_level,
|
||||
emit_debug_info,
|
||||
),
|
||||
OptLevel::Development => gen_from_mono_module_dev(
|
||||
) -> GenFromMono<'a> {
|
||||
match code_gen_options.backend {
|
||||
CodeGenBackend::Assembly => gen_from_mono_module_dev(
|
||||
arena,
|
||||
loaded,
|
||||
target,
|
||||
preprocessed_host_path,
|
||||
wasm_dev_stack_bytes,
|
||||
),
|
||||
CodeGenBackend::Llvm => {
|
||||
gen_from_mono_module_llvm(arena, loaded, roc_file_path, target, code_gen_options)
|
||||
}
|
||||
CodeGenBackend::Wasm => {
|
||||
// emit wasm via the llvm backend
|
||||
gen_from_mono_module_llvm(arena, loaded, roc_file_path, target, code_gen_options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO how should imported modules factor into this? What if those use builtins too?
|
||||
// TODO this should probably use more helper functions
|
||||
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
|
||||
fn gen_from_mono_module_llvm(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
fn gen_from_mono_module_llvm<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
loaded: MonomorphizedModule<'a>,
|
||||
roc_file_path: &Path,
|
||||
target: &target_lexicon::Triple,
|
||||
opt_level: OptLevel,
|
||||
emit_debug_info: bool,
|
||||
) -> (CodeObject, CodeGenTiming) {
|
||||
code_gen_options: CodeGenOptions,
|
||||
) -> GenFromMono<'a> {
|
||||
use crate::target::{self, convert_opt_level};
|
||||
use inkwell::attributes::{Attribute, AttributeLoc};
|
||||
use inkwell::context::Context;
|
||||
|
@ -263,6 +276,12 @@ fn gen_from_mono_module_llvm(
|
|||
}
|
||||
}
|
||||
|
||||
let CodeGenOptions {
|
||||
backend: _,
|
||||
opt_level,
|
||||
emit_debug_info,
|
||||
} = code_gen_options;
|
||||
|
||||
let builder = context.create_builder();
|
||||
let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module);
|
||||
let (mpm, _fpm) = roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level);
|
||||
|
@ -278,7 +297,11 @@ fn gen_from_mono_module_llvm(
|
|||
interns: loaded.interns,
|
||||
module,
|
||||
target_info,
|
||||
mode: LlvmBackendMode::Binary,
|
||||
mode: match opt_level {
|
||||
OptLevel::Development => LlvmBackendMode::BinaryDev,
|
||||
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => LlvmBackendMode::Binary,
|
||||
},
|
||||
|
||||
exposed_to_host: loaded.exposed_to_host.values.keys().copied().collect(),
|
||||
};
|
||||
|
||||
|
@ -430,17 +453,22 @@ fn gen_from_mono_module_llvm(
|
|||
(
|
||||
CodeObject::MemoryBuffer(memory_buffer),
|
||||
CodeGenTiming { code_gen },
|
||||
ExpectMetadata {
|
||||
interns: env.interns,
|
||||
layout_interner: loaded.layout_interner,
|
||||
expectations: loaded.expectations,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-wasm32")]
|
||||
fn gen_from_mono_module_dev(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
fn gen_from_mono_module_dev<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
loaded: MonomorphizedModule<'a>,
|
||||
target: &target_lexicon::Triple,
|
||||
preprocessed_host_path: &Path,
|
||||
wasm_dev_stack_bytes: Option<u32>,
|
||||
) -> (CodeObject, CodeGenTiming) {
|
||||
) -> GenFromMono<'a> {
|
||||
use target_lexicon::Architecture;
|
||||
|
||||
match target.architecture {
|
||||
|
@ -458,13 +486,13 @@ fn gen_from_mono_module_dev(
|
|||
}
|
||||
|
||||
#[cfg(not(feature = "target-wasm32"))]
|
||||
pub fn gen_from_mono_module_dev(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
pub fn gen_from_mono_module_dev<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
loaded: MonomorphizedModule<'a>,
|
||||
target: &target_lexicon::Triple,
|
||||
_host_input_path: &Path,
|
||||
_wasm_dev_stack_bytes: Option<u32>,
|
||||
) -> (CodeObject, CodeGenTiming) {
|
||||
) -> GenFromMono<'a> {
|
||||
use target_lexicon::Architecture;
|
||||
|
||||
match target.architecture {
|
||||
|
@ -476,12 +504,12 @@ pub fn gen_from_mono_module_dev(
|
|||
}
|
||||
|
||||
#[cfg(feature = "target-wasm32")]
|
||||
fn gen_from_mono_module_dev_wasm32(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
fn gen_from_mono_module_dev_wasm32<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
loaded: MonomorphizedModule<'a>,
|
||||
preprocessed_host_path: &Path,
|
||||
wasm_dev_stack_bytes: Option<u32>,
|
||||
) -> (CodeObject, CodeGenTiming) {
|
||||
) -> GenFromMono<'a> {
|
||||
let code_gen_start = Instant::now();
|
||||
let MonomorphizedModule {
|
||||
module_id,
|
||||
|
@ -530,14 +558,19 @@ fn gen_from_mono_module_dev_wasm32(
|
|||
(
|
||||
CodeObject::Vector(final_binary_bytes),
|
||||
CodeGenTiming { code_gen },
|
||||
ExpectMetadata {
|
||||
interns,
|
||||
layout_interner,
|
||||
expectations: loaded.expectations,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn gen_from_mono_module_dev_assembly(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
fn gen_from_mono_module_dev_assembly<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
loaded: MonomorphizedModule<'a>,
|
||||
target: &target_lexicon::Triple,
|
||||
) -> (CodeObject, CodeGenTiming) {
|
||||
) -> GenFromMono<'a> {
|
||||
let code_gen_start = Instant::now();
|
||||
|
||||
let lazy_literals = true;
|
||||
|
@ -569,5 +602,13 @@ fn gen_from_mono_module_dev_assembly(
|
|||
.write()
|
||||
.expect("failed to build output object");
|
||||
|
||||
(CodeObject::Vector(module_out), CodeGenTiming { code_gen })
|
||||
(
|
||||
CodeObject::Vector(module_out),
|
||||
CodeGenTiming { code_gen },
|
||||
ExpectMetadata {
|
||||
interns,
|
||||
layout_interner,
|
||||
expectations: loaded.expectations,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const SIGUSR1: c_int = 10;
|
||||
|
||||
|
@ -18,6 +19,71 @@ pub fn setSharedBuffer(ptr: [*]u8, length: usize) callconv(.C) usize {
|
|||
return 0;
|
||||
}
|
||||
|
||||
pub fn expectFailedStart() callconv(.C) [*]u8 {
|
||||
pub fn expectFailedStartSharedBuffer() callconv(.C) [*]u8 {
|
||||
return SHARED_BUFFER.ptr;
|
||||
}
|
||||
|
||||
pub fn expectFailedStartSharedFile() callconv(.C) [*]u8 {
|
||||
// IMPORTANT: shared memory object names must begin with / and contain no other slashes!
|
||||
var name: [100]u8 = undefined;
|
||||
_ = std.fmt.bufPrint(name[0..100], "/roc_expect_buffer_{}\x00", .{roc_getppid()}) catch unreachable;
|
||||
|
||||
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
|
||||
const shared_fd = roc_shm_open(@ptrCast(*const i8, &name), O_RDWR | O_CREAT, 0o666);
|
||||
|
||||
const length = 4096;
|
||||
|
||||
const shared_ptr = roc_mmap(
|
||||
null,
|
||||
length,
|
||||
PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
shared_fd,
|
||||
0,
|
||||
);
|
||||
|
||||
const ptr = @ptrCast([*]u8, shared_ptr);
|
||||
|
||||
return ptr;
|
||||
} else {
|
||||
unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
extern fn roc_send_signal(pid: c_int, sig: c_int) c_int;
|
||||
extern fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
|
||||
extern fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
|
||||
extern fn roc_getppid() c_int;
|
||||
|
||||
pub fn readSharedBufferEnv() callconv(.C) void {
|
||||
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
|
||||
|
||||
// IMPORTANT: shared memory object names must begin with / and contain no other slashes!
|
||||
var name: [100]u8 = undefined;
|
||||
_ = std.fmt.bufPrint(name[0..100], "/roc_expect_buffer_{}\x00", .{roc_getppid()}) catch unreachable;
|
||||
|
||||
const shared_fd = roc_shm_open(@ptrCast(*const i8, &name), O_RDWR | O_CREAT, 0o666);
|
||||
const length = 4096;
|
||||
|
||||
const shared_ptr = roc_mmap(
|
||||
null,
|
||||
length,
|
||||
PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
shared_fd,
|
||||
0,
|
||||
);
|
||||
|
||||
const ptr = @ptrCast([*]u8, shared_ptr);
|
||||
|
||||
SHARED_BUFFER = ptr[0..length];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expectFailedFinalize() callconv(.C) void {
|
||||
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
|
||||
const parent_pid = roc_getppid();
|
||||
|
||||
_ = roc_send_signal(parent_pid, SIGUSR1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -169,10 +169,14 @@ comptime {
|
|||
@export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });
|
||||
|
||||
if (builtin.target.cpu.arch != .wasm32) {
|
||||
exportUtilsFn(expect.expectFailedStart, "expect_failed_start");
|
||||
exportUtilsFn(expect.expectFailedStartSharedBuffer, "expect_failed_start_shared_buffer");
|
||||
exportUtilsFn(expect.expectFailedStartSharedFile, "expect_failed_start_shared_file");
|
||||
exportUtilsFn(expect.expectFailedFinalize, "expect_failed_finalize");
|
||||
|
||||
// sets the buffer used for expect failures
|
||||
@export(expect.setSharedBuffer, .{ .name = "set_shared_buffer", .linkage = .Weak });
|
||||
|
||||
exportUtilsFn(expect.readSharedBufferEnv, "read_env_shared_buffer");
|
||||
}
|
||||
|
||||
if (builtin.target.cpu.arch == .aarch64) {
|
||||
|
|
|
@ -22,6 +22,24 @@ extern fn roc_panic(c_ptr: *const anyopaque, tag_id: u32) callconv(.C) void;
|
|||
// should work just like libc memcpy (we can't assume libc is present)
|
||||
extern fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
|
||||
|
||||
extern fn kill(pid: c_int, sig: c_int) c_int;
|
||||
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
|
||||
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
|
||||
extern fn getppid() c_int;
|
||||
|
||||
fn testing_roc_getppid() callconv(.C) c_int {
|
||||
return getppid();
|
||||
}
|
||||
fn testing_roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
|
||||
return kill(pid, sig);
|
||||
}
|
||||
fn testing_roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
|
||||
return shm_open(name, oflag, mode);
|
||||
}
|
||||
fn testing_roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
|
||||
return mmap(addr, length, prot, flags, fd, offset);
|
||||
}
|
||||
|
||||
comptime {
|
||||
const builtin = @import("builtin");
|
||||
// During tests, use the testing allocators to satisfy these functions.
|
||||
|
@ -31,6 +49,13 @@ comptime {
|
|||
@export(testing_roc_dealloc, .{ .name = "roc_dealloc", .linkage = .Strong });
|
||||
@export(testing_roc_panic, .{ .name = "roc_panic", .linkage = .Strong });
|
||||
@export(testing_roc_memcpy, .{ .name = "roc_memcpy", .linkage = .Strong });
|
||||
|
||||
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
|
||||
@export(testing_roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
|
||||
@export(testing_roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
|
||||
@export(testing_roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
|
||||
@export(testing_roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -215,6 +215,7 @@ fn run_command(mut command: Command, flaky_fail_counter: usize) {
|
|||
// Flaky test errors that only occur sometimes on MacOS ci server.
|
||||
if error_str.contains("FileNotFound")
|
||||
|| error_str.contains("unable to save cached ZIR code")
|
||||
|| error_str.contains("LLVM failed to emit asm")
|
||||
{
|
||||
if flaky_fail_counter == 10 {
|
||||
panic!("{} failed 10 times in a row. The following error is unlikely to be a flaky error: {}", command_str, error_str);
|
||||
|
|
|
@ -368,30 +368,26 @@ contains = \list, needle ->
|
|||
## You can use it in a pipeline:
|
||||
##
|
||||
## [2, 4, 8]
|
||||
## |> List.walk { start: 0, step: Num.add }
|
||||
## |> List.walk 0 Num.add
|
||||
##
|
||||
## This returns 14 because:
|
||||
## * `state` starts at 0 (because of `start: 0`)
|
||||
## * `state` starts at 0
|
||||
## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`.
|
||||
##
|
||||
## Here is a table of how `state` changes as [List.walk] walks over the elements
|
||||
## `[2, 4, 8]` using #Num.add as its `step` function to determine the next `state`.
|
||||
## `[2, 4, 8]` using [Num.add] as its `step` function to determine the next `state`.
|
||||
##
|
||||
## `state` | `elem` | `step state elem` (`Num.add state elem`)
|
||||
## --------+--------+-----------------------------------------
|
||||
## state | elem | Num.add state elem
|
||||
## :---: | :---: | :----------------:
|
||||
## 0 | |
|
||||
## 0 | 2 | 2
|
||||
## 2 | 4 | 6
|
||||
## 6 | 8 | 14
|
||||
##
|
||||
## So `state` goes through these changes:
|
||||
## 1. `0` (because of `start: 0`)
|
||||
## 2. `1` (because of `Num.add state elem` with `state` = 0 and `elem` = 1
|
||||
## The following returns -6:
|
||||
##
|
||||
## [1, 2, 3]
|
||||
## |> List.walk { start: 0, step: Num.sub }
|
||||
##
|
||||
## This returns -6 because
|
||||
## |> List.walk 0 Num.sub
|
||||
##
|
||||
## Note that in other languages, `walk` is sometimes called `reduce`,
|
||||
## `fold`, `foldLeft`, or `foldl`.
|
||||
|
|
|
@ -404,8 +404,12 @@ pub const UTILS_INCREF: &str = "roc_builtins.utils.incref";
|
|||
pub const UTILS_DECREF: &str = "roc_builtins.utils.decref";
|
||||
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";
|
||||
|
||||
pub const UTILS_EXPECT_FAILED_START: &str = "roc_builtins.utils.expect_failed_start";
|
||||
pub const UTILS_EXPECT_FAILED_START_SHARED_BUFFER: &str =
|
||||
"roc_builtins.utils.expect_failed_start_shared_buffer";
|
||||
pub const UTILS_EXPECT_FAILED_START_SHARED_FILE: &str =
|
||||
"roc_builtins.utils.expect_failed_start_shared_file";
|
||||
pub const UTILS_EXPECT_FAILED_FINALIZE: &str = "roc_builtins.utils.expect_failed_finalize";
|
||||
pub const UTILS_EXPECT_READ_ENV_SHARED_BUFFER: &str = "roc_builtins.utils.read_env_shared_buffer";
|
||||
|
||||
pub const UTILS_LONGJMP: &str = "longjmp";
|
||||
pub const UTILS_SETJMP: &str = "setjmp";
|
||||
|
|
|
@ -163,6 +163,7 @@ impl<'a, 'ctx> Scope<'a, 'ctx> {
|
|||
pub enum LlvmBackendMode {
|
||||
/// Assumes primitives (roc_alloc, roc_panic, etc) are provided by the host
|
||||
Binary,
|
||||
BinaryDev,
|
||||
/// Creates a test wrapper around the main roc function to catch and report panics.
|
||||
/// Provides a testing implementation of primitives (roc_alloc, roc_panic, etc)
|
||||
GenTest,
|
||||
|
@ -174,6 +175,7 @@ impl LlvmBackendMode {
|
|||
pub(crate) fn has_host(self) -> bool {
|
||||
match self {
|
||||
LlvmBackendMode::Binary => true,
|
||||
LlvmBackendMode::BinaryDev => true,
|
||||
LlvmBackendMode::GenTest => false,
|
||||
LlvmBackendMode::WasmGenTest => true,
|
||||
LlvmBackendMode::CliTest => false,
|
||||
|
@ -184,6 +186,7 @@ impl LlvmBackendMode {
|
|||
fn returns_roc_result(self) -> bool {
|
||||
match self {
|
||||
LlvmBackendMode::Binary => false,
|
||||
LlvmBackendMode::BinaryDev => false,
|
||||
LlvmBackendMode::GenTest => true,
|
||||
LlvmBackendMode::WasmGenTest => true,
|
||||
LlvmBackendMode::CliTest => true,
|
||||
|
@ -193,6 +196,7 @@ impl LlvmBackendMode {
|
|||
fn runs_expects(self) -> bool {
|
||||
match self {
|
||||
LlvmBackendMode::Binary => false,
|
||||
LlvmBackendMode::BinaryDev => true,
|
||||
LlvmBackendMode::GenTest => false,
|
||||
LlvmBackendMode::WasmGenTest => false,
|
||||
LlvmBackendMode::CliTest => true,
|
||||
|
@ -2824,6 +2828,10 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
|||
lookups,
|
||||
);
|
||||
|
||||
if let LlvmBackendMode::BinaryDev = env.mode {
|
||||
crate::llvm::expect::finalize(env);
|
||||
}
|
||||
|
||||
bd.build_unconditional_branch(then_block);
|
||||
}
|
||||
roc_target::PtrWidth::Bytes4 => {
|
||||
|
@ -3925,7 +3933,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
|
|||
)
|
||||
}
|
||||
|
||||
LlvmBackendMode::Binary => {}
|
||||
LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev => {}
|
||||
}
|
||||
|
||||
// a generic version that writes the result into a passed *u8 pointer
|
||||
|
@ -3976,7 +3984,9 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
|
|||
roc_result_type(env, roc_function.get_type().get_return_type().unwrap()).into()
|
||||
}
|
||||
|
||||
LlvmBackendMode::Binary => basic_type_from_layout(env, &return_layout),
|
||||
LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev => {
|
||||
basic_type_from_layout(env, &return_layout)
|
||||
}
|
||||
};
|
||||
|
||||
let size: BasicValueEnum = return_type.size_of().unwrap().into();
|
||||
|
@ -4948,7 +4958,7 @@ pub fn build_proc<'a, 'ctx, 'env>(
|
|||
GenTest | WasmGenTest | CliTest => {
|
||||
/* no host, or exposing types is not supported */
|
||||
}
|
||||
Binary => {
|
||||
Binary | BinaryDev => {
|
||||
for (alias_name, (generated_function, top_level, layout)) in aliases.iter() {
|
||||
expose_alias_to_host(
|
||||
env,
|
||||
|
|
|
@ -14,7 +14,8 @@ use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
|
|||
use roc_region::all::Region;
|
||||
|
||||
use super::build::{
|
||||
add_func, load_roc_value, load_symbol_and_layout, use_roc_value, FunctionSpec, Scope,
|
||||
add_func, load_roc_value, load_symbol_and_layout, use_roc_value, FunctionSpec, LlvmBackendMode,
|
||||
Scope,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
@ -93,6 +94,16 @@ fn write_state<'a, 'ctx, 'env>(
|
|||
env.builder.build_store(offset_ptr, offset);
|
||||
}
|
||||
|
||||
pub(crate) fn finalize(env: &Env) {
|
||||
let func = env
|
||||
.module
|
||||
.get_function(bitcode::UTILS_EXPECT_FAILED_FINALIZE)
|
||||
.unwrap();
|
||||
|
||||
env.builder
|
||||
.build_call(func, &[], "call_expect_failed_finalize");
|
||||
}
|
||||
|
||||
pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
|
@ -101,10 +112,13 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
|
|||
region: Region,
|
||||
lookups: &[Symbol],
|
||||
) {
|
||||
let func = env
|
||||
.module
|
||||
.get_function(bitcode::UTILS_EXPECT_FAILED_START)
|
||||
.unwrap();
|
||||
let start_function = if let LlvmBackendMode::BinaryDev = env.mode {
|
||||
bitcode::UTILS_EXPECT_FAILED_START_SHARED_FILE
|
||||
} else {
|
||||
bitcode::UTILS_EXPECT_FAILED_START_SHARED_BUFFER
|
||||
};
|
||||
|
||||
let func = env.module.get_function(start_function).unwrap();
|
||||
|
||||
let call_result = env
|
||||
.builder
|
||||
|
|
|
@ -155,10 +155,32 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) {
|
|||
}
|
||||
}
|
||||
|
||||
unreachable_function(env, "roc_getppid");
|
||||
unreachable_function(env, "roc_mmap");
|
||||
unreachable_function(env, "roc_send_signal");
|
||||
unreachable_function(env, "roc_shm_open");
|
||||
|
||||
add_sjlj_roc_panic(env)
|
||||
}
|
||||
}
|
||||
|
||||
fn unreachable_function(env: &Env, name: &str) {
|
||||
// The type of this function (but not the implementation) should have
|
||||
// already been defined by the builtins, which rely on it.
|
||||
let fn_val = env.module.get_function(name).unwrap();
|
||||
|
||||
// Add a basic block for the entry point
|
||||
let entry = env.context.append_basic_block(fn_val, "entry");
|
||||
|
||||
env.builder.position_at_end(entry);
|
||||
|
||||
env.builder.build_unreachable();
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
crate::llvm::build::verify_fn(fn_val);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
|
||||
let ctx = env.context;
|
||||
let module = env.module;
|
||||
|
|
|
@ -120,7 +120,11 @@ macro_rules! run_jit_function {
|
|||
|
||||
$transform(success)
|
||||
}
|
||||
Err(error_msg) => panic!("Roc failed with message: {}", error_msg),
|
||||
Err(error_msg) => {
|
||||
eprintln!("This Roc code crashed with: \"{error_msg}\"");
|
||||
|
||||
Expr::MalformedClosure
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@ const SKIP_SUBS_CACHE: bool = {
|
|||
|
||||
pub use roc_load_internal::docs;
|
||||
pub use roc_load_internal::file::{
|
||||
EntryPoint, ExecutionMode, Expectations, LoadConfig, LoadResult, LoadStart, LoadedModule,
|
||||
LoadingProblem, MonomorphizedModule, Phase, Threading,
|
||||
EntryPoint, ExecutionMode, ExpectMetadata, Expectations, LoadConfig, LoadResult, LoadStart,
|
||||
LoadedModule, LoadingProblem, MonomorphizedModule, Phase, Threading,
|
||||
};
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
|
|
@ -705,6 +705,13 @@ pub struct MonomorphizedModule<'a> {
|
|||
pub expectations: VecMap<ModuleId, Expectations>,
|
||||
}
|
||||
|
||||
/// Values used to render expect output
|
||||
pub struct ExpectMetadata<'a> {
|
||||
pub interns: Interns,
|
||||
pub layout_interner: SingleThreadedInterner<'a, Layout<'a>>,
|
||||
pub expectations: VecMap<ModuleId, Expectations>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EntryPoint<'a> {
|
||||
Executable {
|
||||
|
|
|
@ -615,6 +615,14 @@ impl<'a> CommentOrNewline<'a> {
|
|||
DocComment(comment_str) => format!("##{}", comment_str),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn comment_str(&'a self) -> Option<&'a str> {
|
||||
match self {
|
||||
CommentOrNewline::LineComment(s) => Some(*s),
|
||||
CommentOrNewline::DocComment(s) => Some(*s),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
|
|
|
@ -56,13 +56,13 @@ pub struct ExprParseOptions {
|
|||
/// This is usually true, but false within list/record literals
|
||||
/// because the comma separating backpassing arguments conflicts
|
||||
/// with the comma separating literal elements
|
||||
accept_multi_backpassing: bool,
|
||||
pub accept_multi_backpassing: bool,
|
||||
|
||||
/// Check for the `->` token, and raise an error if found
|
||||
/// This is usually true, but false in if-guards
|
||||
///
|
||||
/// > Just foo if foo == 2 -> ...
|
||||
check_for_arrow: bool,
|
||||
pub check_for_arrow: bool,
|
||||
}
|
||||
|
||||
impl Default for ExprParseOptions {
|
||||
|
@ -896,6 +896,65 @@ pub fn parse_single_def<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
// This is a macro only because trying to make it be a function caused lifetime issues.
|
||||
#[macro_export]
|
||||
macro_rules! join_ann_to_body {
|
||||
($arena:expr, $loc_pattern:expr, $loc_def_expr:expr, $ann_pattern:expr, $ann_type:expr, $spaces_before_current:expr, $region:expr) => {{
|
||||
// join this body with the preceding annotation
|
||||
|
||||
let value_def = ValueDef::AnnotatedBody {
|
||||
ann_pattern: $arena.alloc(*$ann_pattern),
|
||||
ann_type: $arena.alloc(*$ann_type),
|
||||
comment: $spaces_before_current
|
||||
.first()
|
||||
.and_then($crate::ast::CommentOrNewline::comment_str),
|
||||
body_pattern: $arena.alloc($loc_pattern),
|
||||
body_expr: *$arena.alloc($loc_def_expr),
|
||||
};
|
||||
|
||||
(
|
||||
value_def,
|
||||
roc_region::all::Region::span_across(&$ann_pattern.region, &$region),
|
||||
)
|
||||
}};
|
||||
}
|
||||
|
||||
// This is a macro only because trying to make it be a function caused lifetime issues.
|
||||
#[macro_export]
|
||||
macro_rules! join_alias_to_body {
|
||||
($arena:expr, $loc_pattern:expr, $loc_def_expr:expr, $header:expr, $ann_type:expr, $spaces_before_current:expr, $region:expr) => {{
|
||||
use roc_region::all::Region;
|
||||
|
||||
// This is a case like
|
||||
// UserId x : [UserId Int]
|
||||
// UserId x = UserId 42
|
||||
// We optimistically parsed the first line as an alias; we now turn it
|
||||
// into an annotation.
|
||||
|
||||
let loc_name = $arena.alloc($header.name.map(|x| Pattern::Tag(x)));
|
||||
let ann_pattern = Pattern::Apply(loc_name, $header.vars);
|
||||
|
||||
let vars_region = Region::across_all($header.vars.iter().map(|v| &v.region));
|
||||
let region_ann_pattern = Region::span_across(&loc_name.region, &vars_region);
|
||||
let loc_ann_pattern = Loc::at(region_ann_pattern, ann_pattern);
|
||||
|
||||
let value_def = ValueDef::AnnotatedBody {
|
||||
ann_pattern: $arena.alloc(loc_ann_pattern),
|
||||
ann_type: $arena.alloc(*$ann_type),
|
||||
comment: $spaces_before_current
|
||||
.first()
|
||||
.and_then($crate::ast::CommentOrNewline::comment_str),
|
||||
body_pattern: $arena.alloc($loc_pattern),
|
||||
body_expr: *$arena.alloc($loc_def_expr),
|
||||
};
|
||||
|
||||
(
|
||||
value_def,
|
||||
Region::span_across(&$header.name.region, &$region),
|
||||
)
|
||||
}};
|
||||
}
|
||||
|
||||
fn parse_defs_end<'a>(
|
||||
_options: ExprParseOptions,
|
||||
min_indent: u32,
|
||||
|
@ -920,97 +979,67 @@ fn parse_defs_end<'a>(
|
|||
Either::Second(value_def) => {
|
||||
// If we got a ValueDef::Body, check if a type annotation preceded it.
|
||||
// If so, we may need to combine them into an AnnotatedBody.
|
||||
match value_def {
|
||||
let joined = match value_def {
|
||||
ValueDef::Body(loc_pattern, loc_def_expr)
|
||||
if spaces_before_current.len() <= 1 =>
|
||||
{
|
||||
let region =
|
||||
Region::span_across(&loc_pattern.region, &loc_def_expr.region);
|
||||
|
||||
let comment = match spaces_before_current.get(0) {
|
||||
Some(CommentOrNewline::LineComment(s)) => Some(*s),
|
||||
Some(CommentOrNewline::DocComment(s)) => Some(*s),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
match defs.last() {
|
||||
Some(Err(ValueDef::Annotation(ann_pattern, ann_type))) => {
|
||||
// join this body with the preceding annotation
|
||||
|
||||
let value_def = ValueDef::AnnotatedBody {
|
||||
ann_pattern: arena.alloc(*ann_pattern),
|
||||
ann_type: arena.alloc(*ann_type),
|
||||
comment,
|
||||
body_pattern: arena.alloc(loc_pattern),
|
||||
body_expr: arena.alloc(loc_def_expr),
|
||||
};
|
||||
|
||||
let region =
|
||||
Region::span_across(&ann_pattern.region, ®ion);
|
||||
let (value_def, region) = join_ann_to_body!(
|
||||
arena,
|
||||
loc_pattern,
|
||||
loc_def_expr,
|
||||
ann_pattern,
|
||||
ann_type,
|
||||
spaces_before_current,
|
||||
region
|
||||
);
|
||||
|
||||
defs.replace_with_value_def(
|
||||
defs.tags.len() - 1,
|
||||
value_def,
|
||||
region,
|
||||
)
|
||||
);
|
||||
|
||||
true
|
||||
}
|
||||
Some(Ok(TypeDef::Alias {
|
||||
header,
|
||||
ann: ann_type,
|
||||
})) => {
|
||||
// This is a case like
|
||||
// UserId x : [UserId Int]
|
||||
// UserId x = UserId 42
|
||||
// We optimistically parsed the first line as an alias; we now turn it
|
||||
// into an annotation.
|
||||
|
||||
let loc_name =
|
||||
arena.alloc(header.name.map(|x| Pattern::Tag(x)));
|
||||
let ann_pattern = Pattern::Apply(loc_name, header.vars);
|
||||
|
||||
let vars_region = Region::across_all(
|
||||
header.vars.iter().map(|v| &v.region),
|
||||
let (value_def, region) = join_alias_to_body!(
|
||||
arena,
|
||||
loc_pattern,
|
||||
loc_def_expr,
|
||||
header,
|
||||
ann_type,
|
||||
spaces_before_current,
|
||||
region
|
||||
);
|
||||
let region_ann_pattern =
|
||||
Region::span_across(&loc_name.region, &vars_region);
|
||||
let loc_ann_pattern =
|
||||
Loc::at(region_ann_pattern, ann_pattern);
|
||||
|
||||
let value_def = ValueDef::AnnotatedBody {
|
||||
ann_pattern: arena.alloc(loc_ann_pattern),
|
||||
ann_type: arena.alloc(*ann_type),
|
||||
comment,
|
||||
body_pattern: arena.alloc(loc_pattern),
|
||||
body_expr: arena.alloc(loc_def_expr),
|
||||
};
|
||||
|
||||
let region =
|
||||
Region::span_across(&header.name.region, ®ion);
|
||||
|
||||
defs.replace_with_value_def(
|
||||
defs.tags.len() - 1,
|
||||
value_def,
|
||||
region,
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
// the previous and current def can't be joined up
|
||||
defs.push_value_def(
|
||||
value_def,
|
||||
region,
|
||||
spaces_before_current,
|
||||
&[],
|
||||
);
|
||||
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if !joined {
|
||||
// the previous and current def can't be joined up
|
||||
defs.push_value_def(value_def, region, spaces_before_current, &[]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next_state
|
||||
}
|
||||
|
@ -1021,9 +1050,9 @@ fn parse_defs_end<'a>(
|
|||
}
|
||||
|
||||
pub struct SingleDef<'a> {
|
||||
type_or_value: Either<TypeDef<'a>, ValueDef<'a>>,
|
||||
region: Region,
|
||||
spaces_before: &'a [CommentOrNewline<'a>],
|
||||
pub type_or_value: Either<TypeDef<'a>, ValueDef<'a>>,
|
||||
pub region: Region,
|
||||
pub spaces_before: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
||||
fn parse_defs_expr<'a>(
|
||||
|
|
|
@ -190,6 +190,150 @@ pub enum Problem {
|
|||
},
|
||||
}
|
||||
|
||||
impl Problem {
|
||||
/// Returns a Region value from the Problem, if possible.
|
||||
/// Some problems have more than one region; in those cases,
|
||||
/// this tries to pick the one that's closest to the original
|
||||
/// definition site, since that's what the REPL uses this for:
|
||||
/// filtering out errors and warnings from wrapped defs based
|
||||
/// on their Region being outside the expression currently being evaluated.
|
||||
pub fn region(&self) -> Option<Region> {
|
||||
match self {
|
||||
Problem::UnusedDef(_, region)
|
||||
| Problem::Shadowing {
|
||||
original_region: region,
|
||||
..
|
||||
}
|
||||
| Problem::UnusedImport(_, region)
|
||||
| Problem::UnusedModuleImport(_, region)
|
||||
| Problem::UnknownGeneratesWith(Loc { region, .. })
|
||||
| Problem::UnusedArgument(_, _, _, region)
|
||||
| Problem::UnusedBranchDef(_, region)
|
||||
| Problem::PrecedenceProblem(PrecedenceProblem::BothNonAssociative(region, _, _))
|
||||
| Problem::UnsupportedPattern(_, region)
|
||||
| Problem::CyclicAlias(_, region, _, _)
|
||||
| Problem::PhantomTypeArgument {
|
||||
variable_region: region,
|
||||
..
|
||||
}
|
||||
| Problem::UnboundTypeVariable {
|
||||
one_occurrence: region,
|
||||
..
|
||||
}
|
||||
| Problem::DuplicateRecordFieldValue {
|
||||
record_region: region,
|
||||
..
|
||||
}
|
||||
| Problem::DuplicateRecordFieldType {
|
||||
record_region: region,
|
||||
..
|
||||
}
|
||||
| Problem::InvalidOptionalValue {
|
||||
record_region: region,
|
||||
..
|
||||
}
|
||||
| Problem::DuplicateTag {
|
||||
tag_union_region: region,
|
||||
..
|
||||
}
|
||||
| Problem::RuntimeError(RuntimeError::Shadowing {
|
||||
original_region: region,
|
||||
..
|
||||
})
|
||||
| Problem::RuntimeError(RuntimeError::InvalidOptionalValue {
|
||||
record_region: region,
|
||||
..
|
||||
})
|
||||
| Problem::RuntimeError(RuntimeError::UnsupportedPattern(region))
|
||||
| Problem::RuntimeError(RuntimeError::MalformedPattern(_, region))
|
||||
| Problem::RuntimeError(RuntimeError::LookupNotInScope(Loc { region, .. }, _))
|
||||
| Problem::RuntimeError(RuntimeError::OpaqueNotDefined {
|
||||
usage: Loc { region, .. },
|
||||
..
|
||||
})
|
||||
| Problem::RuntimeError(RuntimeError::OpaqueOutsideScope {
|
||||
referenced_region: region,
|
||||
..
|
||||
})
|
||||
| Problem::RuntimeError(RuntimeError::OpaqueNotApplied(Loc { region, .. }))
|
||||
| Problem::RuntimeError(RuntimeError::OpaqueAppliedToMultipleArgs(region))
|
||||
| Problem::RuntimeError(RuntimeError::ValueNotExposed { region, .. })
|
||||
| Problem::RuntimeError(RuntimeError::ModuleNotImported { region, .. })
|
||||
| Problem::RuntimeError(RuntimeError::InvalidPrecedence(_, region))
|
||||
| Problem::RuntimeError(RuntimeError::MalformedIdentifier(_, _, region))
|
||||
| Problem::RuntimeError(RuntimeError::MalformedTypeName(_, region))
|
||||
| Problem::RuntimeError(RuntimeError::MalformedClosure(region))
|
||||
| Problem::RuntimeError(RuntimeError::InvalidRecordUpdate { region })
|
||||
| Problem::RuntimeError(RuntimeError::InvalidFloat(_, region, _))
|
||||
| Problem::RuntimeError(RuntimeError::InvalidInt(_, _, region, _))
|
||||
| Problem::RuntimeError(RuntimeError::InvalidInterpolation(region))
|
||||
| Problem::RuntimeError(RuntimeError::InvalidHexadecimal(region))
|
||||
| Problem::RuntimeError(RuntimeError::InvalidUnicodeCodePt(region))
|
||||
| Problem::RuntimeError(RuntimeError::EmptySingleQuote(region))
|
||||
| Problem::RuntimeError(RuntimeError::MultipleCharsInSingleQuote(region))
|
||||
| Problem::RuntimeError(RuntimeError::DegenerateBranch(region))
|
||||
| Problem::InvalidAliasRigid { region, .. }
|
||||
| Problem::InvalidInterpolation(region)
|
||||
| Problem::InvalidHexadecimal(region)
|
||||
| Problem::InvalidUnicodeCodePt(region)
|
||||
| Problem::NestedDatatype {
|
||||
def_region: region, ..
|
||||
}
|
||||
| Problem::InvalidExtensionType { region, .. }
|
||||
| Problem::AbilityHasTypeVariables {
|
||||
variables_region: region,
|
||||
..
|
||||
}
|
||||
| Problem::HasClauseIsNotAbility { region }
|
||||
| Problem::IllegalHasClause { region }
|
||||
| Problem::DuplicateHasAbility { region, .. }
|
||||
| Problem::AbilityMemberMissingHasClause { region, .. }
|
||||
| Problem::AbilityMemberMultipleBoundVars {
|
||||
span_has_clauses: region,
|
||||
..
|
||||
}
|
||||
| Problem::AbilityNotOnToplevel { region }
|
||||
| Problem::AbilityUsedAsType(_, _, region)
|
||||
| Problem::NestedSpecialization(_, region)
|
||||
| Problem::IllegalDerivedAbility(region)
|
||||
| Problem::ImplementationNotFound { region, .. }
|
||||
| Problem::NotAnAbilityMember { region, .. }
|
||||
| Problem::OptionalAbilityImpl { region, .. }
|
||||
| Problem::QualifiedAbilityImpl { region }
|
||||
| Problem::AbilityImplNotIdent { region }
|
||||
| Problem::DuplicateImpl {
|
||||
original: region, ..
|
||||
}
|
||||
| Problem::NotAnAbility(region)
|
||||
| Problem::ImplementsNonRequired { region, .. }
|
||||
| Problem::DoesNotImplementAbility { region, .. }
|
||||
| Problem::NoIdentifiersIntroduced(region)
|
||||
| Problem::OverloadedSpecialization {
|
||||
overload: region, ..
|
||||
}
|
||||
| Problem::NotBoundInAllPatterns { region, .. }
|
||||
| Problem::SignatureDefMismatch {
|
||||
def_pattern: region,
|
||||
..
|
||||
}
|
||||
| Problem::MultipleListRestPattern { region }
|
||||
| Problem::UnnecessaryOutputWildcard { region } => Some(*region),
|
||||
Problem::RuntimeError(RuntimeError::CircularDef(cycle_entries))
|
||||
| Problem::BadRecursion(cycle_entries) => {
|
||||
cycle_entries.first().map(|entry| entry.expr_region)
|
||||
}
|
||||
Problem::RuntimeError(RuntimeError::UnresolvedTypeVar)
|
||||
| Problem::RuntimeError(RuntimeError::ErroneousType)
|
||||
| Problem::RuntimeError(RuntimeError::NonExhaustivePattern)
|
||||
| Problem::RuntimeError(RuntimeError::NoImplementation)
|
||||
| Problem::RuntimeError(RuntimeError::VoidValue)
|
||||
| Problem::RuntimeError(RuntimeError::ExposedButNotDefined(_))
|
||||
| Problem::RuntimeError(RuntimeError::NoImplementationNamed { .. })
|
||||
| Problem::ExposedButNotDefined(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ExtensionTypeKind {
|
||||
Record,
|
||||
|
|
|
@ -244,6 +244,7 @@ fn create_llvm_module<'a>(
|
|||
};
|
||||
let (main_fn_name, main_fn) = match config.mode {
|
||||
LlvmBackendMode::Binary => unreachable!(),
|
||||
LlvmBackendMode::BinaryDev => unreachable!(),
|
||||
LlvmBackendMode::CliTest => unreachable!(),
|
||||
LlvmBackendMode::WasmGenTest => roc_gen_llvm::llvm::build::build_wasm_test_wrapper(
|
||||
&env,
|
||||
|
|
|
@ -920,7 +920,7 @@ fn markdown_to_html(
|
|||
}
|
||||
};
|
||||
|
||||
let markdown_options = pulldown_cmark::Options::empty();
|
||||
let markdown_options = pulldown_cmark::Options::ENABLE_TABLES;
|
||||
|
||||
let mut expecting_code_block = false;
|
||||
|
||||
|
|
|
@ -37,6 +37,10 @@ a {
|
|||
color: #972395;
|
||||
}
|
||||
|
||||
table tr th, table tr td {
|
||||
padding: 6px 13px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
|
|
@ -76,13 +76,23 @@ fn collect_roc_definitions<'a>(object: &object::File<'a, &'a [u8]>) -> MutMap<St
|
|||
|
||||
let address = sym.address() as u64;
|
||||
|
||||
// special exceptions for memcpy and memset.
|
||||
if name == "roc_memcpy" {
|
||||
vaddresses.insert("memcpy".to_string(), address);
|
||||
} else if name == "roc_memset" {
|
||||
vaddresses.insert("memset".to_string(), address);
|
||||
} else if name == "roc_memmove" {
|
||||
vaddresses.insert("memmove".to_string(), address);
|
||||
// special exceptions for roc_ functions that map to libc symbols
|
||||
let direct_mapping = match name {
|
||||
"roc_memcpy" => Some("memcpy"),
|
||||
"roc_memset" => Some("memset"),
|
||||
"roc_memmove" => Some("memmove"),
|
||||
|
||||
// for expects
|
||||
"roc_mmap" => Some("mmap"),
|
||||
"roc_getppid" => Some("getppid"),
|
||||
"roc_send_signal" => Some("kill"),
|
||||
"roc_shm_open" => Some("shm_open"),
|
||||
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(libc_symbol) = direct_mapping {
|
||||
vaddresses.insert(libc_symbol.to_string(), address);
|
||||
}
|
||||
|
||||
vaddresses.insert(name.to_string(), address);
|
||||
|
|
|
@ -23,6 +23,7 @@ libloading = "0.7.1"
|
|||
rustyline = {git = "https://github.com/roc-lang/rustyline", rev = "e74333c"}
|
||||
rustyline-derive = {git = "https://github.com/roc-lang/rustyline", rev = "e74333c"}
|
||||
target-lexicon = "0.12.2"
|
||||
unicode-segmentation = "1.10.0"
|
||||
|
||||
roc_build = {path = "../compiler/build"}
|
||||
roc_builtins = {path = "../compiler/builtins"}
|
||||
|
|
281
crates/repl_cli/src/cli_gen.rs
Normal file
281
crates/repl_cli/src/cli_gen.rs
Normal file
|
@ -0,0 +1,281 @@
|
|||
use bumpalo::Bump;
|
||||
use inkwell::context::Context;
|
||||
use libloading::Library;
|
||||
use roc_build::link::llvm_module_to_dylib;
|
||||
use roc_collections::all::MutSet;
|
||||
use roc_gen_llvm::llvm::build::LlvmBackendMode;
|
||||
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
||||
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type};
|
||||
use roc_intern::SingleThreadedInterner;
|
||||
use roc_load::{EntryPoint, MonomorphizedModule};
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_mono::layout::Layout;
|
||||
use roc_parse::ast::Expr;
|
||||
use roc_repl_eval::eval::jit_to_ast;
|
||||
use roc_repl_eval::gen::{compile_to_mono, format_answer, Problems, ReplOutput};
|
||||
use roc_repl_eval::{ReplApp, ReplAppMemory};
|
||||
use roc_reporting::report::DEFAULT_PALETTE;
|
||||
use roc_std::RocStr;
|
||||
use roc_target::TargetInfo;
|
||||
use roc_types::pretty_print::{name_and_print_var, DebugPrint};
|
||||
use roc_types::subs::Subs;
|
||||
use target_lexicon::Triple;
|
||||
|
||||
pub fn gen_and_eval_llvm<'a, I: Iterator<Item = &'a str>>(
|
||||
defs: I,
|
||||
src: &str,
|
||||
target: Triple,
|
||||
opt_level: OptLevel,
|
||||
) -> (Option<ReplOutput>, Problems) {
|
||||
let arena = Bump::new();
|
||||
let target_info = TargetInfo::from(&target);
|
||||
|
||||
let mut loaded;
|
||||
let problems;
|
||||
|
||||
match compile_to_mono(&arena, defs, src, target_info, DEFAULT_PALETTE) {
|
||||
(Some(mono), probs) => {
|
||||
loaded = mono;
|
||||
problems = probs;
|
||||
}
|
||||
(None, probs) => {
|
||||
return (None, probs);
|
||||
}
|
||||
};
|
||||
|
||||
debug_assert_eq!(loaded.exposed_to_host.values.len(), 1);
|
||||
let (main_fn_symbol, main_fn_var) = loaded.exposed_to_host.values.iter().next().unwrap();
|
||||
let main_fn_symbol = *main_fn_symbol;
|
||||
let main_fn_var = *main_fn_var;
|
||||
|
||||
// pretty-print the expr type string for later.
|
||||
let expr_type_str = name_and_print_var(
|
||||
main_fn_var,
|
||||
&mut loaded.subs,
|
||||
loaded.module_id,
|
||||
&loaded.interns,
|
||||
DebugPrint::NOTHING,
|
||||
);
|
||||
|
||||
let (_, main_fn_layout) = match loaded.procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
|
||||
Some(layout) => *layout,
|
||||
None => {
|
||||
let empty_vec: Vec<String> = Vec::new(); // rustc can't infer the type of this Vec.
|
||||
debug_assert_ne!(problems.errors, empty_vec, "Got no errors but also no valid layout for the generated main function in the repl!");
|
||||
|
||||
return (None, problems);
|
||||
}
|
||||
};
|
||||
|
||||
let interns = loaded.interns.clone();
|
||||
|
||||
let (lib, main_fn_name, subs, layout_interner) =
|
||||
mono_module_to_dylib(&arena, target, loaded, opt_level).expect("we produce a valid Dylib");
|
||||
|
||||
let mut app = CliApp { lib };
|
||||
|
||||
let expr = jit_to_ast(
|
||||
&arena,
|
||||
&mut app,
|
||||
main_fn_name,
|
||||
main_fn_layout,
|
||||
main_fn_var,
|
||||
&subs,
|
||||
&interns,
|
||||
layout_interner.into_global().fork(),
|
||||
target_info,
|
||||
);
|
||||
let expr_str = format_answer(&arena, expr).to_string();
|
||||
|
||||
(
|
||||
Some(ReplOutput {
|
||||
expr: expr_str,
|
||||
expr_type: expr_type_str,
|
||||
}),
|
||||
problems,
|
||||
)
|
||||
}
|
||||
|
||||
struct CliApp {
|
||||
lib: Library,
|
||||
}
|
||||
|
||||
struct CliMemory;
|
||||
|
||||
impl<'a> ReplApp<'a> for CliApp {
|
||||
type Memory = CliMemory;
|
||||
|
||||
/// Run user code that returns a type with a `Builtin` layout
|
||||
/// Size of the return value is statically determined from its Rust type
|
||||
fn call_function<Return, F>(&mut self, main_fn_name: &str, mut transform: F) -> Expr<'a>
|
||||
where
|
||||
F: FnMut(&'a Self::Memory, Return) -> Expr<'a>,
|
||||
Self::Memory: 'a,
|
||||
{
|
||||
run_jit_function!(self.lib, main_fn_name, Return, |v| transform(&CliMemory, v))
|
||||
}
|
||||
|
||||
/// Run user code that returns a struct or union, whose size is provided as an argument
|
||||
fn call_function_dynamic_size<T, F>(
|
||||
&mut self,
|
||||
main_fn_name: &str,
|
||||
ret_bytes: usize,
|
||||
mut transform: F,
|
||||
) -> T
|
||||
where
|
||||
F: FnMut(&'a Self::Memory, usize) -> T,
|
||||
Self::Memory: 'a,
|
||||
{
|
||||
run_jit_function_dynamic_type!(self.lib, main_fn_name, ret_bytes, |v| transform(
|
||||
&CliMemory, v
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! deref_number {
|
||||
($name: ident, $t: ty) => {
|
||||
fn $name(&self, addr: usize) -> $t {
|
||||
let ptr = addr as *const _;
|
||||
unsafe { *ptr }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl ReplAppMemory for CliMemory {
|
||||
deref_number!(deref_bool, bool);
|
||||
|
||||
deref_number!(deref_u8, u8);
|
||||
deref_number!(deref_u16, u16);
|
||||
deref_number!(deref_u32, u32);
|
||||
deref_number!(deref_u64, u64);
|
||||
deref_number!(deref_u128, u128);
|
||||
deref_number!(deref_usize, usize);
|
||||
|
||||
deref_number!(deref_i8, i8);
|
||||
deref_number!(deref_i16, i16);
|
||||
deref_number!(deref_i32, i32);
|
||||
deref_number!(deref_i64, i64);
|
||||
deref_number!(deref_i128, i128);
|
||||
deref_number!(deref_isize, isize);
|
||||
|
||||
deref_number!(deref_f32, f32);
|
||||
deref_number!(deref_f64, f64);
|
||||
|
||||
fn deref_str(&self, addr: usize) -> &str {
|
||||
let reference: &RocStr = unsafe { std::mem::transmute(addr) };
|
||||
reference.as_str()
|
||||
}
|
||||
|
||||
fn deref_pointer_with_tag_id(&self, addr: usize) -> (u16, u64) {
|
||||
let addr_with_id = self.deref_usize(addr);
|
||||
let tag_id_mask = 0b111;
|
||||
|
||||
let tag_id = addr_with_id & tag_id_mask;
|
||||
let data_addr = addr_with_id & !tag_id_mask;
|
||||
(tag_id as _, data_addr as _)
|
||||
}
|
||||
}
|
||||
|
||||
fn mono_module_to_dylib<'a>(
|
||||
arena: &'a Bump,
|
||||
target: Triple,
|
||||
loaded: MonomorphizedModule<'a>,
|
||||
opt_level: OptLevel,
|
||||
) -> Result<
|
||||
(
|
||||
libloading::Library,
|
||||
&'a str,
|
||||
Subs,
|
||||
SingleThreadedInterner<'a, Layout<'a>>,
|
||||
),
|
||||
libloading::Error,
|
||||
> {
|
||||
let target_info = TargetInfo::from(&target);
|
||||
|
||||
let MonomorphizedModule {
|
||||
procedures,
|
||||
entry_point,
|
||||
interns,
|
||||
subs,
|
||||
layout_interner,
|
||||
..
|
||||
} = loaded;
|
||||
|
||||
let context = Context::create();
|
||||
let builder = context.create_builder();
|
||||
let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins(
|
||||
&target, &context, "",
|
||||
));
|
||||
|
||||
let module = arena.alloc(module);
|
||||
let (module_pass, function_pass) =
|
||||
roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level);
|
||||
|
||||
let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module);
|
||||
|
||||
// Compile and add all the Procs before adding main
|
||||
let env = roc_gen_llvm::llvm::build::Env {
|
||||
arena,
|
||||
layout_interner: &layout_interner,
|
||||
builder: &builder,
|
||||
dibuilder: &dibuilder,
|
||||
compile_unit: &compile_unit,
|
||||
context: &context,
|
||||
interns,
|
||||
module,
|
||||
target_info,
|
||||
mode: LlvmBackendMode::GenTest, // so roc_panic is generated
|
||||
// important! we don't want any procedures to get the C calling convention
|
||||
exposed_to_host: MutSet::default(),
|
||||
};
|
||||
|
||||
// Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no
|
||||
// platform to provide them.
|
||||
add_default_roc_externs(&env);
|
||||
|
||||
let entry_point = match entry_point {
|
||||
EntryPoint::Executable { symbol, layout, .. } => {
|
||||
roc_mono::ir::EntryPoint { symbol, layout }
|
||||
}
|
||||
EntryPoint::Test => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main(
|
||||
&env,
|
||||
opt_level,
|
||||
procedures,
|
||||
entry_point,
|
||||
);
|
||||
|
||||
env.dibuilder.finalize();
|
||||
|
||||
// we don't use the debug info, and it causes weird errors.
|
||||
module.strip_debug_info();
|
||||
|
||||
// Uncomment this to see the module's un-optimized LLVM instruction output:
|
||||
// env.module.print_to_stderr();
|
||||
|
||||
if main_fn.verify(true) {
|
||||
function_pass.run_on(&main_fn);
|
||||
} else {
|
||||
panic!("Main function {} failed LLVM verification in build. Uncomment things nearby to see more details.", main_fn_name);
|
||||
}
|
||||
|
||||
module_pass.run_on(env.module);
|
||||
|
||||
// Uncomment this to see the module's optimized LLVM instruction output:
|
||||
// env.module.print_to_stderr();
|
||||
|
||||
// Verify the module
|
||||
if let Err(errors) = env.module.verify() {
|
||||
panic!(
|
||||
"Errors defining module:\n{}\n\nUncomment things nearby to see more details.",
|
||||
errors.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
llvm_module_to_dylib(env.module, &target, opt_level)
|
||||
.map(|lib| (lib, main_fn_name, subs, layout_interner))
|
||||
}
|
4
crates/repl_cli/src/colors.rs
Normal file
4
crates/repl_cli/src/colors.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub const BLUE: &str = "\u{001b}[36m";
|
||||
pub const PINK: &str = "\u{001b}[35m";
|
||||
pub const GREEN: &str = "\u{001b}[32m";
|
||||
pub const END_COL: &str = "\u{001b}[0m";
|
|
@ -1,38 +1,13 @@
|
|||
//! Command Line Interface (CLI) functionality for the Read-Evaluate-Print-Loop (REPL).
|
||||
use bumpalo::Bump;
|
||||
mod cli_gen;
|
||||
mod colors;
|
||||
pub mod repl_state;
|
||||
|
||||
use colors::{BLUE, END_COL, PINK};
|
||||
use const_format::concatcp;
|
||||
use inkwell::context::Context;
|
||||
use libloading::Library;
|
||||
use roc_gen_llvm::llvm::build::LlvmBackendMode;
|
||||
use roc_intern::SingleThreadedInterner;
|
||||
use roc_mono::layout::Layout;
|
||||
use roc_types::subs::Subs;
|
||||
use rustyline::highlight::{Highlighter, PromptInfo};
|
||||
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
|
||||
use rustyline_derive::{Completer, Helper, Hinter};
|
||||
use std::borrow::Cow;
|
||||
use std::io;
|
||||
use target_lexicon::Triple;
|
||||
use repl_state::ReplState;
|
||||
|
||||
use roc_build::link::llvm_module_to_dylib;
|
||||
use roc_collections::all::MutSet;
|
||||
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
||||
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type};
|
||||
use roc_load::{EntryPoint, MonomorphizedModule};
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_parse::ast::Expr;
|
||||
use roc_parse::parser::{EClosure, EExpr, SyntaxError};
|
||||
use roc_repl_eval::eval::jit_to_ast;
|
||||
use roc_repl_eval::gen::{compile_to_mono, format_answer, ReplOutput};
|
||||
use roc_repl_eval::{ReplApp, ReplAppMemory};
|
||||
use roc_reporting::report::DEFAULT_PALETTE;
|
||||
use roc_std::RocStr;
|
||||
use roc_target::TargetInfo;
|
||||
use roc_types::pretty_print::{name_and_print_var, DebugPrint};
|
||||
|
||||
const BLUE: &str = "\u{001b}[36m";
|
||||
const PINK: &str = "\u{001b}[35m";
|
||||
const END_COL: &str = "\u{001b}[0m";
|
||||
use crate::repl_state::PROMPT;
|
||||
|
||||
pub const WELCOME_MESSAGE: &str = concatcp!(
|
||||
"\n The rockin’ ",
|
||||
|
@ -50,480 +25,54 @@ pub const WELCOME_MESSAGE: &str = concatcp!(
|
|||
// TODO add link to repl tutorial(does not yet exist).
|
||||
pub const SHORT_INSTRUCTIONS: &str = "Enter an expression, or :help, or :q to quit.\n\n";
|
||||
|
||||
// TODO add link to repl tutorial(does not yet exist).
|
||||
pub const TIPS: &str = concatcp!(
|
||||
BLUE,
|
||||
" - ",
|
||||
END_COL,
|
||||
"Entered code needs to return something. For example:\n\n",
|
||||
PINK,
|
||||
" » foo = 1\n … foo\n\n",
|
||||
END_COL,
|
||||
BLUE,
|
||||
" - ",
|
||||
END_COL,
|
||||
"You can use `Ctrl+V`+`Ctrl+J` to make a newline. The repl will also insert a newline if you press enter",
|
||||
" when the current expression can not be evaluated, e.g. when you type `foo =<ENTER>`.\n\n",
|
||||
BLUE,
|
||||
" - ",
|
||||
END_COL,
|
||||
":q to quit\n\n",
|
||||
BLUE,
|
||||
" - ",
|
||||
END_COL,
|
||||
":help\n"
|
||||
);
|
||||
pub const PROMPT: &str = concatcp!("\n", BLUE, "»", END_COL, " ");
|
||||
pub const CONT_PROMPT: &str = concatcp!(BLUE, "…", END_COL, " ");
|
||||
|
||||
#[derive(Completer, Helper, Hinter)]
|
||||
struct ReplHelper {
|
||||
validator: InputValidator,
|
||||
pending_src: String,
|
||||
}
|
||||
|
||||
impl ReplHelper {
|
||||
pub(crate) fn new() -> ReplHelper {
|
||||
ReplHelper {
|
||||
validator: InputValidator::new(),
|
||||
pending_src: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Highlighter for ReplHelper {
|
||||
fn has_continuation_prompt(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
|
||||
&'s self,
|
||||
prompt: &'p str,
|
||||
info: PromptInfo<'_>,
|
||||
) -> Cow<'b, str> {
|
||||
if info.line_no() > 0 {
|
||||
CONT_PROMPT.into()
|
||||
} else {
|
||||
prompt.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Validator for ReplHelper {
|
||||
fn validate(
|
||||
&self,
|
||||
ctx: &mut validate::ValidationContext,
|
||||
) -> rustyline::Result<validate::ValidationResult> {
|
||||
self.validator.validate(ctx)
|
||||
}
|
||||
|
||||
fn validate_while_typing(&self) -> bool {
|
||||
self.validator.validate_while_typing()
|
||||
}
|
||||
}
|
||||
|
||||
struct InputValidator {}
|
||||
|
||||
impl InputValidator {
|
||||
pub(crate) fn new() -> InputValidator {
|
||||
InputValidator {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Validator for InputValidator {
|
||||
fn validate(&self, ctx: &mut ValidationContext) -> rustyline::Result<ValidationResult> {
|
||||
if ctx.input().is_empty() {
|
||||
Ok(ValidationResult::Incomplete)
|
||||
} else {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let state = roc_parse::state::State::new(ctx.input().trim().as_bytes());
|
||||
|
||||
match roc_parse::expr::parse_loc_expr(&arena, state, 0) {
|
||||
// Special case some syntax errors to allow for multi-line inputs
|
||||
Err((_, EExpr::DefMissingFinalExpr(_), _))
|
||||
| Err((_, EExpr::DefMissingFinalExpr2(_, _), _))
|
||||
| Err((_, EExpr::Closure(EClosure::Body(_, _), _), _)) => {
|
||||
Ok(ValidationResult::Incomplete)
|
||||
}
|
||||
_ => Ok(ValidationResult::Valid(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CliApp {
|
||||
lib: Library,
|
||||
}
|
||||
|
||||
struct CliMemory;
|
||||
|
||||
impl<'a> ReplApp<'a> for CliApp {
|
||||
type Memory = CliMemory;
|
||||
|
||||
/// Run user code that returns a type with a `Builtin` layout
|
||||
/// Size of the return value is statically determined from its Rust type
|
||||
fn call_function<Return, F>(&mut self, main_fn_name: &str, mut transform: F) -> Expr<'a>
|
||||
where
|
||||
F: FnMut(&'a Self::Memory, Return) -> Expr<'a>,
|
||||
Self::Memory: 'a,
|
||||
{
|
||||
run_jit_function!(self.lib, main_fn_name, Return, |v| transform(&CliMemory, v))
|
||||
}
|
||||
|
||||
/// Run user code that returns a struct or union, whose size is provided as an argument
|
||||
fn call_function_dynamic_size<T, F>(
|
||||
&mut self,
|
||||
main_fn_name: &str,
|
||||
ret_bytes: usize,
|
||||
mut transform: F,
|
||||
) -> T
|
||||
where
|
||||
F: FnMut(&'a Self::Memory, usize) -> T,
|
||||
Self::Memory: 'a,
|
||||
{
|
||||
run_jit_function_dynamic_type!(self.lib, main_fn_name, ret_bytes, |v| transform(
|
||||
&CliMemory, v
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! deref_number {
|
||||
($name: ident, $t: ty) => {
|
||||
fn $name(&self, addr: usize) -> $t {
|
||||
let ptr = addr as *const _;
|
||||
unsafe { *ptr }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl ReplAppMemory for CliMemory {
|
||||
deref_number!(deref_bool, bool);
|
||||
|
||||
deref_number!(deref_u8, u8);
|
||||
deref_number!(deref_u16, u16);
|
||||
deref_number!(deref_u32, u32);
|
||||
deref_number!(deref_u64, u64);
|
||||
deref_number!(deref_u128, u128);
|
||||
deref_number!(deref_usize, usize);
|
||||
|
||||
deref_number!(deref_i8, i8);
|
||||
deref_number!(deref_i16, i16);
|
||||
deref_number!(deref_i32, i32);
|
||||
deref_number!(deref_i64, i64);
|
||||
deref_number!(deref_i128, i128);
|
||||
deref_number!(deref_isize, isize);
|
||||
|
||||
deref_number!(deref_f32, f32);
|
||||
deref_number!(deref_f64, f64);
|
||||
|
||||
fn deref_str(&self, addr: usize) -> &str {
|
||||
let reference: &RocStr = unsafe { std::mem::transmute(addr) };
|
||||
reference.as_str()
|
||||
}
|
||||
|
||||
fn deref_pointer_with_tag_id(&self, addr: usize) -> (u16, u64) {
|
||||
let addr_with_id = self.deref_usize(addr);
|
||||
let tag_id_mask = 0b111;
|
||||
|
||||
let tag_id = addr_with_id & tag_id_mask;
|
||||
let data_addr = addr_with_id & !tag_id_mask;
|
||||
(tag_id as _, data_addr as _)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mono_module_to_dylib<'a>(
|
||||
arena: &'a Bump,
|
||||
target: Triple,
|
||||
loaded: MonomorphizedModule<'a>,
|
||||
opt_level: OptLevel,
|
||||
) -> Result<
|
||||
(
|
||||
libloading::Library,
|
||||
&'a str,
|
||||
Subs,
|
||||
SingleThreadedInterner<'a, Layout<'a>>,
|
||||
),
|
||||
libloading::Error,
|
||||
> {
|
||||
let target_info = TargetInfo::from(&target);
|
||||
|
||||
let MonomorphizedModule {
|
||||
procedures,
|
||||
entry_point,
|
||||
interns,
|
||||
subs,
|
||||
layout_interner,
|
||||
..
|
||||
} = loaded;
|
||||
|
||||
let context = Context::create();
|
||||
let builder = context.create_builder();
|
||||
let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins(
|
||||
&target, &context, "",
|
||||
));
|
||||
|
||||
let module = arena.alloc(module);
|
||||
let (module_pass, function_pass) =
|
||||
roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level);
|
||||
|
||||
let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module);
|
||||
|
||||
// Compile and add all the Procs before adding main
|
||||
let env = roc_gen_llvm::llvm::build::Env {
|
||||
arena,
|
||||
layout_interner: &layout_interner,
|
||||
builder: &builder,
|
||||
dibuilder: &dibuilder,
|
||||
compile_unit: &compile_unit,
|
||||
context: &context,
|
||||
interns,
|
||||
module,
|
||||
target_info,
|
||||
mode: LlvmBackendMode::GenTest, // so roc_panic is generated
|
||||
// important! we don't want any procedures to get the C calling convention
|
||||
exposed_to_host: MutSet::default(),
|
||||
};
|
||||
|
||||
// Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no
|
||||
// platform to provide them.
|
||||
add_default_roc_externs(&env);
|
||||
|
||||
let entry_point = match entry_point {
|
||||
EntryPoint::Executable { symbol, layout, .. } => {
|
||||
roc_mono::ir::EntryPoint { symbol, layout }
|
||||
}
|
||||
EntryPoint::Test => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main(
|
||||
&env,
|
||||
opt_level,
|
||||
procedures,
|
||||
entry_point,
|
||||
);
|
||||
|
||||
env.dibuilder.finalize();
|
||||
|
||||
// we don't use the debug info, and it causes weird errors.
|
||||
module.strip_debug_info();
|
||||
|
||||
// Uncomment this to see the module's un-optimized LLVM instruction output:
|
||||
// env.module.print_to_stderr();
|
||||
|
||||
if main_fn.verify(true) {
|
||||
function_pass.run_on(&main_fn);
|
||||
} else {
|
||||
panic!("Main function {} failed LLVM verification in build. Uncomment things nearby to see more details.", main_fn_name);
|
||||
}
|
||||
|
||||
module_pass.run_on(env.module);
|
||||
|
||||
// Uncomment this to see the module's optimized LLVM instruction output:
|
||||
// env.module.print_to_stderr();
|
||||
|
||||
// Verify the module
|
||||
if let Err(errors) = env.module.verify() {
|
||||
panic!(
|
||||
"Errors defining module:\n{}\n\nUncomment things nearby to see more details.",
|
||||
errors.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
llvm_module_to_dylib(env.module, &target, opt_level)
|
||||
.map(|lib| (lib, main_fn_name, subs, layout_interner))
|
||||
}
|
||||
|
||||
fn gen_and_eval_llvm<'a>(
|
||||
src: &str,
|
||||
target: Triple,
|
||||
opt_level: OptLevel,
|
||||
) -> Result<ReplOutput, SyntaxError<'a>> {
|
||||
let arena = Bump::new();
|
||||
let target_info = TargetInfo::from(&target);
|
||||
|
||||
let mut loaded = match compile_to_mono(&arena, src, target_info, DEFAULT_PALETTE) {
|
||||
Ok(x) => x,
|
||||
Err(prob_strings) => {
|
||||
return Ok(ReplOutput::Problems(prob_strings));
|
||||
}
|
||||
};
|
||||
|
||||
debug_assert_eq!(loaded.exposed_to_host.values.len(), 1);
|
||||
let (main_fn_symbol, main_fn_var) = loaded.exposed_to_host.values.iter().next().unwrap();
|
||||
let main_fn_symbol = *main_fn_symbol;
|
||||
let main_fn_var = *main_fn_var;
|
||||
|
||||
// pretty-print the expr type string for later.
|
||||
let expr_type_str = name_and_print_var(
|
||||
main_fn_var,
|
||||
&mut loaded.subs,
|
||||
loaded.module_id,
|
||||
&loaded.interns,
|
||||
DebugPrint::NOTHING,
|
||||
);
|
||||
|
||||
let (_, main_fn_layout) = match loaded.procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
|
||||
Some(layout) => *layout,
|
||||
None => {
|
||||
return Ok(ReplOutput::NoProblems {
|
||||
expr: "<function>".to_string(),
|
||||
expr_type: expr_type_str,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let interns = loaded.interns.clone();
|
||||
|
||||
let (lib, main_fn_name, subs, layout_interner) =
|
||||
mono_module_to_dylib(&arena, target, loaded, opt_level).expect("we produce a valid Dylib");
|
||||
|
||||
let mut app = CliApp { lib };
|
||||
|
||||
let res_answer = jit_to_ast(
|
||||
&arena,
|
||||
&mut app,
|
||||
main_fn_name,
|
||||
main_fn_layout,
|
||||
main_fn_var,
|
||||
&subs,
|
||||
&interns,
|
||||
layout_interner.into_global().fork(),
|
||||
target_info,
|
||||
);
|
||||
|
||||
let formatted = format_answer(&arena, res_answer, expr_type_str);
|
||||
Ok(formatted)
|
||||
}
|
||||
|
||||
fn eval_and_format<'a>(src: &str) -> Result<String, SyntaxError<'a>> {
|
||||
let format_output = |output| match output {
|
||||
ReplOutput::NoProblems { expr, expr_type } => {
|
||||
format!("\n{} {}:{} {}", expr, PINK, END_COL, expr_type)
|
||||
}
|
||||
ReplOutput::Problems(lines) => format!("\n{}\n", lines.join("\n\n")),
|
||||
};
|
||||
|
||||
gen_and_eval_llvm(src, Triple::host(), OptLevel::Normal).map(format_output)
|
||||
}
|
||||
|
||||
fn report_parse_error(fail: SyntaxError) {
|
||||
println!("TODO Gracefully report parse error in repl: {:?}", fail);
|
||||
}
|
||||
|
||||
pub fn main() -> io::Result<()> {
|
||||
pub fn main() -> i32 {
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::Editor;
|
||||
|
||||
// To debug rustyline:
|
||||
// <UNCOMMENT> env_logger::init();
|
||||
// <RUN WITH:> RUST_LOG=rustyline=debug cargo run repl 2> debug.log
|
||||
print!("{}{}", WELCOME_MESSAGE, TIPS);
|
||||
print!("{}{}", WELCOME_MESSAGE, SHORT_INSTRUCTIONS);
|
||||
|
||||
let mut prev_line_blank = false;
|
||||
let mut editor = Editor::<ReplHelper>::new();
|
||||
let repl_helper = ReplHelper::new();
|
||||
let mut editor = Editor::<ReplState>::new();
|
||||
let repl_helper = ReplState::new();
|
||||
editor.set_helper(Some(repl_helper));
|
||||
|
||||
loop {
|
||||
let readline = editor.readline(PROMPT);
|
||||
|
||||
match readline {
|
||||
match editor.readline(PROMPT) {
|
||||
Ok(line) => {
|
||||
let trim_line = line.trim();
|
||||
editor.add_history_entry(trim_line);
|
||||
editor.add_history_entry(line.trim());
|
||||
|
||||
let pending_src = &mut editor
|
||||
.helper_mut()
|
||||
.expect("Editor helper was not set")
|
||||
.pending_src;
|
||||
let dimensions = editor.dimensions();
|
||||
let repl_helper = editor.helper_mut().expect("Editor helper was not set");
|
||||
|
||||
match trim_line.to_lowercase().as_str() {
|
||||
"" => {
|
||||
if pending_src.is_empty() {
|
||||
print!("\n{}", SHORT_INSTRUCTIONS);
|
||||
} else if prev_line_blank {
|
||||
// After two blank lines in a row, give up and try parsing it
|
||||
// even though it's going to fail. This way you don't get stuck.
|
||||
match eval_and_format(pending_src.as_str()) {
|
||||
match repl_helper.step(&line, dimensions) {
|
||||
Ok(output) => {
|
||||
// If there was no output, don't print a blank line!
|
||||
// (This happens for something like a type annotation.)
|
||||
if !output.is_empty() {
|
||||
println!("{}", output);
|
||||
}
|
||||
Err(fail) => {
|
||||
report_parse_error(fail);
|
||||
}
|
||||
}
|
||||
|
||||
pending_src.clear();
|
||||
} else {
|
||||
pending_src.push('\n');
|
||||
|
||||
prev_line_blank = true;
|
||||
continue; // Skip the part where we reset prev_line_blank to false
|
||||
}
|
||||
}
|
||||
":help" => {
|
||||
// TODO add link to repl tutorial(does not yet exist).
|
||||
println!("Use :q to exit.");
|
||||
}
|
||||
":exit" | ":quit" | ":q" => {
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
let result = if pending_src.is_empty() {
|
||||
eval_and_format(trim_line)
|
||||
} else {
|
||||
pending_src.push('\n');
|
||||
pending_src.push_str(trim_line);
|
||||
|
||||
eval_and_format(pending_src.as_str())
|
||||
Err(exit_code) => return exit_code,
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(output) => {
|
||||
println!("{}", output);
|
||||
pending_src.clear();
|
||||
}
|
||||
// Err(Fail {
|
||||
// reason: FailReason::Eof(_),
|
||||
// ..
|
||||
// }) => {}
|
||||
Err(fail) => {
|
||||
report_parse_error(fail);
|
||||
pending_src.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(ReadlineError::Interrupted) => {
|
||||
println!("CTRL-C");
|
||||
break;
|
||||
#[cfg(windows)]
|
||||
Err(ReadlineError::WindowResize) => {
|
||||
// This is fine; just ignore it.
|
||||
}
|
||||
Err(ReadlineError::Eof) => {
|
||||
// If we hit an eof, and we're allowed to keep going,
|
||||
// append the str to the src we're building up and continue.
|
||||
// (We only need to append it here if it was empty before;
|
||||
// otherwise, we already appended it before calling eval_and_format.)
|
||||
let pending_src = &mut editor
|
||||
.helper_mut()
|
||||
.expect("Editor helper was not set")
|
||||
.pending_src;
|
||||
|
||||
if pending_src.is_empty() {
|
||||
pending_src.push_str("");
|
||||
// End of input; we're done!
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
Err(ReadlineError::Interrupted) => {
|
||||
eprintln!("CTRL-C");
|
||||
return 1;
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("REPL error: {:?}", err);
|
||||
break;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
prev_line_blank = false;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
630
crates/repl_cli/src/repl_state.rs
Normal file
630
crates/repl_cli/src/repl_state.rs
Normal file
|
@ -0,0 +1,630 @@
|
|||
use crate::cli_gen::gen_and_eval_llvm;
|
||||
use crate::colors::{BLUE, END_COL, GREEN, PINK};
|
||||
use bumpalo::Bump;
|
||||
use const_format::concatcp;
|
||||
use roc_collections::MutSet;
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_parse::ast::{Expr, Pattern, TypeDef, TypeHeader, ValueDef};
|
||||
use roc_parse::expr::{parse_single_def, ExprParseOptions, SingleDef};
|
||||
use roc_parse::parser::{EClosure, EExpr, EPattern};
|
||||
use roc_parse::parser::{EWhen, Either};
|
||||
use roc_parse::state::State;
|
||||
use roc_parse::{join_alias_to_body, join_ann_to_body};
|
||||
use roc_region::all::Loc;
|
||||
use roc_repl_eval::gen::{Problems, ReplOutput};
|
||||
use rustyline::highlight::{Highlighter, PromptInfo};
|
||||
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
|
||||
use rustyline_derive::{Completer, Helper, Hinter};
|
||||
use std::borrow::Cow;
|
||||
use target_lexicon::Triple;
|
||||
|
||||
pub const PROMPT: &str = concatcp!(BLUE, "»", END_COL, " ");
|
||||
pub const CONT_PROMPT: &str = concatcp!(BLUE, "…", END_COL, " ");
|
||||
|
||||
/// The prefix we use for the automatic variable names we assign to each expr,
|
||||
/// e.g. if the prefix is "val" then the first expr you enter will be named "val1"
|
||||
pub const AUTO_VAR_PREFIX: &str = "val";
|
||||
|
||||
// TODO add link to repl tutorial(does not yet exist).
|
||||
pub const TIPS: &str = concatcp!(
|
||||
"\nEnter an expression to evaluate, or a definition (like ",
|
||||
BLUE,
|
||||
"x = 1",
|
||||
END_COL,
|
||||
") to use in future expressions.\n\nUnless there was a compile-time error, expressions get automatically named so you can refer to them later.\nFor example, if you see ",
|
||||
GREEN,
|
||||
"# val1",
|
||||
END_COL,
|
||||
" after an output, you can now refer to that expression as ",
|
||||
BLUE,
|
||||
"val1",
|
||||
END_COL,
|
||||
" in future expressions.\n\nTips:\n\n",
|
||||
BLUE,
|
||||
" - ",
|
||||
END_COL,
|
||||
PINK,
|
||||
"ctrl-v",
|
||||
END_COL,
|
||||
" + ",
|
||||
PINK,
|
||||
"ctrl-j",
|
||||
END_COL,
|
||||
" makes a newline\n\n",
|
||||
BLUE,
|
||||
" - ",
|
||||
END_COL,
|
||||
":q to quit\n\n",
|
||||
BLUE,
|
||||
" - ",
|
||||
END_COL,
|
||||
":help"
|
||||
);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
struct PastDef {
|
||||
ident: String,
|
||||
src: String,
|
||||
}
|
||||
|
||||
#[derive(Completer, Helper, Hinter)]
|
||||
pub struct ReplState {
|
||||
validator: InputValidator,
|
||||
past_defs: Vec<PastDef>,
|
||||
past_def_idents: MutSet<String>,
|
||||
last_auto_ident: u64,
|
||||
}
|
||||
|
||||
impl Default for ReplState {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ReplState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
validator: InputValidator::new(),
|
||||
past_defs: Default::default(),
|
||||
past_def_idents: Default::default(),
|
||||
last_auto_ident: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn step(&mut self, line: &str, dimensions: Option<(usize, usize)>) -> Result<String, i32> {
|
||||
let arena = Bump::new();
|
||||
|
||||
match parse_src(&arena, line) {
|
||||
ParseOutcome::Empty => {
|
||||
if line.is_empty() {
|
||||
Ok(TIPS.to_string())
|
||||
} else if line.ends_with('\n') {
|
||||
// After two blank lines in a row, give up and try parsing it
|
||||
// even though it's going to fail. This way you don't get stuck
|
||||
// in a perpetual Incomplete state due to a syntax error.
|
||||
Ok(self.eval_and_format(line, dimensions))
|
||||
} else {
|
||||
// The previous line wasn't blank, but the line isn't empty either.
|
||||
// This could mean that, for example, you're writing a multiline `when`
|
||||
// and want to add a blank line. No problem! Print a blank line and
|
||||
// continue waiting for input.
|
||||
//
|
||||
// If the user presses enter again, next time prev_line_blank() will be true
|
||||
// and we'll try parsing the source as-is.
|
||||
Ok("\n".to_string())
|
||||
}
|
||||
}
|
||||
ParseOutcome::Expr(_)
|
||||
| ParseOutcome::ValueDef(_)
|
||||
| ParseOutcome::TypeDef(_)
|
||||
| ParseOutcome::SyntaxErr
|
||||
| ParseOutcome::Incomplete => Ok(self.eval_and_format(line, dimensions)),
|
||||
ParseOutcome::Help => {
|
||||
// TODO add link to repl tutorial(does not yet exist).
|
||||
Ok(TIPS.to_string())
|
||||
}
|
||||
ParseOutcome::Exit => Err(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_and_format(&mut self, src: &str, dimensions: Option<(usize, usize)>) -> String {
|
||||
let arena = Bump::new();
|
||||
let pending_past_def;
|
||||
let mut opt_var_name;
|
||||
let src = match parse_src(&arena, src) {
|
||||
ParseOutcome::Expr(_) | ParseOutcome::Incomplete | ParseOutcome::SyntaxErr => {
|
||||
pending_past_def = None;
|
||||
// If it's a SyntaxErr (or Incomplete at this point, meaning it will
|
||||
// become a SyntaxErr as soon as we evaluate it),
|
||||
// proceed as normal and let the error reporting happen during eval.
|
||||
opt_var_name = None;
|
||||
|
||||
src
|
||||
}
|
||||
ParseOutcome::ValueDef(value_def) => {
|
||||
match value_def {
|
||||
ValueDef::Annotation(
|
||||
Loc {
|
||||
value: Pattern::Identifier(ident),
|
||||
..
|
||||
},
|
||||
_,
|
||||
) => {
|
||||
// Record the standalone type annotation for future use.
|
||||
self.add_past_def(ident.trim_end().to_string(), src.to_string());
|
||||
|
||||
// Return early without running eval, since standalone annotations
|
||||
// cannnot be evaluated as expressions.
|
||||
return String::new();
|
||||
}
|
||||
ValueDef::Body(
|
||||
Loc {
|
||||
value: Pattern::Identifier(ident),
|
||||
..
|
||||
},
|
||||
_,
|
||||
)
|
||||
| ValueDef::AnnotatedBody {
|
||||
body_pattern:
|
||||
Loc {
|
||||
value: Pattern::Identifier(ident),
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
pending_past_def = Some((ident.to_string(), src.to_string()));
|
||||
opt_var_name = Some(ident.to_string());
|
||||
|
||||
// Recreate the body of the def and then evaluate it as a lookup.
|
||||
// We do this so that any errors will get reported as part of this expr;
|
||||
// if we just did a lookup on the past def, then errors wouldn't get
|
||||
// reported because we filter out errors whose regions are in past defs.
|
||||
let mut buf = bumpalo::collections::string::String::with_capacity_in(
|
||||
ident.len() + src.len() + 1,
|
||||
&arena,
|
||||
);
|
||||
|
||||
buf.push_str(src);
|
||||
buf.push('\n');
|
||||
buf.push_str(ident);
|
||||
|
||||
buf.into_bump_str()
|
||||
}
|
||||
ValueDef::Annotation(_, _)
|
||||
| ValueDef::Body(_, _)
|
||||
| ValueDef::AnnotatedBody { .. } => {
|
||||
todo!("handle pattern other than identifier (which repl doesn't support)")
|
||||
}
|
||||
ValueDef::Expect { .. } => {
|
||||
todo!("handle receiving an `expect` - what should the repl do for that?")
|
||||
}
|
||||
ValueDef::ExpectFx { .. } => {
|
||||
todo!("handle receiving an `expect-fx` - what should the repl do for that?")
|
||||
}
|
||||
}
|
||||
}
|
||||
ParseOutcome::TypeDef(TypeDef::Alias {
|
||||
header:
|
||||
TypeHeader {
|
||||
name: Loc { value: ident, .. },
|
||||
..
|
||||
},
|
||||
..
|
||||
})
|
||||
| ParseOutcome::TypeDef(TypeDef::Opaque {
|
||||
header:
|
||||
TypeHeader {
|
||||
name: Loc { value: ident, .. },
|
||||
..
|
||||
},
|
||||
..
|
||||
})
|
||||
| ParseOutcome::TypeDef(TypeDef::Ability {
|
||||
header:
|
||||
TypeHeader {
|
||||
name: Loc { value: ident, .. },
|
||||
..
|
||||
},
|
||||
..
|
||||
}) => {
|
||||
// Record the type for future use.
|
||||
self.add_past_def(ident.trim_end().to_string(), src.to_string());
|
||||
|
||||
// Return early without running eval, since none of these
|
||||
// can be evaluated as expressions.
|
||||
return String::new();
|
||||
}
|
||||
ParseOutcome::Empty | ParseOutcome::Help | ParseOutcome::Exit => unreachable!(),
|
||||
};
|
||||
|
||||
// Record e.g. "val1" as a past def, unless our input was exactly the name of
|
||||
// an existing identifer (e.g. I just typed "val1" into the prompt - there's no
|
||||
// need to reassign "val1" to "val2" just because I wanted to see what its value was!)
|
||||
let (output, problems) =
|
||||
match opt_var_name.or_else(|| self.past_def_idents.get(src.trim()).cloned()) {
|
||||
Some(existing_ident) => {
|
||||
opt_var_name = Some(existing_ident);
|
||||
|
||||
gen_and_eval_llvm(
|
||||
self.past_defs.iter().map(|def| def.src.as_str()),
|
||||
src,
|
||||
Triple::host(),
|
||||
OptLevel::Normal,
|
||||
)
|
||||
}
|
||||
None => {
|
||||
let (output, problems) = gen_and_eval_llvm(
|
||||
self.past_defs.iter().map(|def| def.src.as_str()),
|
||||
src,
|
||||
Triple::host(),
|
||||
OptLevel::Normal,
|
||||
);
|
||||
|
||||
// Don't persist defs that have compile errors
|
||||
if problems.errors.is_empty() {
|
||||
let var_name = format!("{AUTO_VAR_PREFIX}{}", self.next_auto_ident());
|
||||
let src = format!("{var_name} = {}", src.trim_end());
|
||||
|
||||
opt_var_name = Some(var_name.clone());
|
||||
|
||||
self.add_past_def(var_name, src);
|
||||
} else {
|
||||
opt_var_name = None;
|
||||
}
|
||||
|
||||
(output, problems)
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((ident, src)) = pending_past_def {
|
||||
self.add_past_def(ident, src);
|
||||
}
|
||||
|
||||
format_output(output, problems, opt_var_name, dimensions)
|
||||
}
|
||||
|
||||
fn next_auto_ident(&mut self) -> u64 {
|
||||
self.last_auto_ident += 1;
|
||||
self.last_auto_ident
|
||||
}
|
||||
|
||||
fn add_past_def(&mut self, ident: String, src: String) {
|
||||
let existing_idents = &mut self.past_def_idents;
|
||||
|
||||
existing_idents.insert(ident.clone());
|
||||
|
||||
self.past_defs.push(PastDef { ident, src });
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum ParseOutcome<'a> {
|
||||
ValueDef(ValueDef<'a>),
|
||||
TypeDef(TypeDef<'a>),
|
||||
Expr(Expr<'a>),
|
||||
Incomplete,
|
||||
SyntaxErr,
|
||||
Empty,
|
||||
Help,
|
||||
Exit,
|
||||
}
|
||||
|
||||
fn parse_src<'a>(arena: &'a Bump, line: &'a str) -> ParseOutcome<'a> {
|
||||
match line.trim().to_lowercase().as_str() {
|
||||
"" => ParseOutcome::Empty,
|
||||
":help" => ParseOutcome::Help,
|
||||
":exit" | ":quit" | ":q" => ParseOutcome::Exit,
|
||||
_ => {
|
||||
let src_bytes = line.as_bytes();
|
||||
|
||||
match roc_parse::expr::parse_loc_expr(arena, State::new(src_bytes), 0) {
|
||||
Ok((_, loc_expr, _)) => ParseOutcome::Expr(loc_expr.value),
|
||||
// Special case some syntax errors to allow for multi-line inputs
|
||||
Err((_, EExpr::Closure(EClosure::Body(_, _), _), _))
|
||||
| Err((_, EExpr::When(EWhen::Pattern(EPattern::Start(_), _), _), _))
|
||||
| Err((_, EExpr::Start(_), _))
|
||||
| Err((_, EExpr::IndentStart(_), _)) => ParseOutcome::Incomplete,
|
||||
Err((_, EExpr::DefMissingFinalExpr(_), _))
|
||||
| Err((_, EExpr::DefMissingFinalExpr2(_, _), _)) => {
|
||||
// This indicates that we had an attempted def; re-parse it as a single-line def.
|
||||
match parse_single_def(
|
||||
ExprParseOptions {
|
||||
accept_multi_backpassing: true,
|
||||
check_for_arrow: true,
|
||||
},
|
||||
0,
|
||||
arena,
|
||||
State::new(src_bytes),
|
||||
) {
|
||||
Ok((
|
||||
_,
|
||||
Some(SingleDef {
|
||||
type_or_value: Either::First(TypeDef::Alias { header, ann }),
|
||||
..
|
||||
}),
|
||||
state,
|
||||
)) => {
|
||||
// This *could* be an AnnotatedBody, e.g. in a case like this:
|
||||
//
|
||||
// UserId x : [UserId Int]
|
||||
// UserId x = UserId 42
|
||||
//
|
||||
// We optimistically parsed the first line as an alias; we might now
|
||||
// turn it into an annotation.
|
||||
match parse_single_def(
|
||||
ExprParseOptions {
|
||||
accept_multi_backpassing: true,
|
||||
check_for_arrow: true,
|
||||
},
|
||||
0,
|
||||
arena,
|
||||
state,
|
||||
) {
|
||||
Ok((
|
||||
_,
|
||||
Some(SingleDef {
|
||||
type_or_value:
|
||||
Either::Second(ValueDef::Body(loc_pattern, loc_def_expr)),
|
||||
region,
|
||||
spaces_before,
|
||||
}),
|
||||
_,
|
||||
)) if spaces_before.len() <= 1 => {
|
||||
// This was, in fact, an AnnotatedBody! Build and return it.
|
||||
let (value_def, _) = join_alias_to_body!(
|
||||
arena,
|
||||
loc_pattern,
|
||||
loc_def_expr,
|
||||
header,
|
||||
&ann,
|
||||
spaces_before,
|
||||
region
|
||||
);
|
||||
|
||||
ParseOutcome::ValueDef(value_def)
|
||||
}
|
||||
_ => {
|
||||
// This was not an AnnotatedBody, so return the alias.
|
||||
ParseOutcome::TypeDef(TypeDef::Alias { header, ann })
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((
|
||||
_,
|
||||
Some(SingleDef {
|
||||
type_or_value:
|
||||
Either::Second(ValueDef::Annotation(ann_pattern, ann_type)),
|
||||
..
|
||||
}),
|
||||
state,
|
||||
)) => {
|
||||
// This *could* be an AnnotatedBody, if the next line is a body.
|
||||
match parse_single_def(
|
||||
ExprParseOptions {
|
||||
accept_multi_backpassing: true,
|
||||
check_for_arrow: true,
|
||||
},
|
||||
0,
|
||||
arena,
|
||||
state,
|
||||
) {
|
||||
Ok((
|
||||
_,
|
||||
Some(SingleDef {
|
||||
type_or_value:
|
||||
Either::Second(ValueDef::Body(loc_pattern, loc_def_expr)),
|
||||
region,
|
||||
spaces_before,
|
||||
}),
|
||||
_,
|
||||
)) if spaces_before.len() <= 1 => {
|
||||
// Inlining this borrow makes clippy unhappy for some reason.
|
||||
let ann_pattern = &ann_pattern;
|
||||
|
||||
// This was, in fact, an AnnotatedBody! Build and return it.
|
||||
let (value_def, _) = join_ann_to_body!(
|
||||
arena,
|
||||
loc_pattern,
|
||||
loc_def_expr,
|
||||
ann_pattern,
|
||||
&ann_type,
|
||||
spaces_before,
|
||||
region
|
||||
);
|
||||
|
||||
ParseOutcome::ValueDef(value_def)
|
||||
}
|
||||
_ => {
|
||||
// This was not an AnnotatedBody, so return the standalone annotation.
|
||||
ParseOutcome::ValueDef(ValueDef::Annotation(
|
||||
ann_pattern,
|
||||
ann_type,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((
|
||||
_,
|
||||
Some(SingleDef {
|
||||
type_or_value: Either::First(type_def),
|
||||
..
|
||||
}),
|
||||
_,
|
||||
)) => ParseOutcome::TypeDef(type_def),
|
||||
Ok((
|
||||
_,
|
||||
Some(SingleDef {
|
||||
type_or_value: Either::Second(value_def),
|
||||
..
|
||||
}),
|
||||
_,
|
||||
)) => ParseOutcome::ValueDef(value_def),
|
||||
Ok((_, None, _)) => {
|
||||
todo!("TODO determine appropriate ParseOutcome for Ok(None)")
|
||||
}
|
||||
Err(_) => ParseOutcome::SyntaxErr,
|
||||
}
|
||||
}
|
||||
Err(_) => ParseOutcome::SyntaxErr,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct InputValidator {}
|
||||
|
||||
impl InputValidator {
|
||||
pub fn new() -> InputValidator {
|
||||
InputValidator {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Validator for InputValidator {
|
||||
fn validate(&self, ctx: &mut ValidationContext) -> rustyline::Result<ValidationResult> {
|
||||
if is_incomplete(ctx.input()) {
|
||||
Ok(ValidationResult::Incomplete)
|
||||
} else {
|
||||
Ok(ValidationResult::Valid(None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_incomplete(input: &str) -> bool {
|
||||
let arena = Bump::new();
|
||||
|
||||
match parse_src(&arena, input) {
|
||||
ParseOutcome::Incomplete => !input.ends_with('\n'),
|
||||
// Standalone annotations are default incomplete, because we can't know
|
||||
// whether they're about to annotate a body on the next line
|
||||
// (or if not, meaning they stay standalone) until you press Enter again!
|
||||
//
|
||||
// So it's Incomplete until you've pressed Enter again (causing the input to end in "\n")
|
||||
ParseOutcome::ValueDef(ValueDef::Annotation(_, _)) if !input.ends_with('\n') => true,
|
||||
ParseOutcome::Expr(Expr::When(_, _)) => {
|
||||
// There might be lots of `when` branches, so don't assume the user is done entering
|
||||
// them until they enter a blank line!
|
||||
!input.ends_with('\n')
|
||||
}
|
||||
ParseOutcome::Empty
|
||||
| ParseOutcome::Help
|
||||
| ParseOutcome::Exit
|
||||
| ParseOutcome::ValueDef(_)
|
||||
| ParseOutcome::TypeDef(_)
|
||||
| ParseOutcome::SyntaxErr
|
||||
| ParseOutcome::Expr(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
impl Highlighter for ReplState {
|
||||
fn has_continuation_prompt(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
|
||||
&'s self,
|
||||
prompt: &'p str,
|
||||
info: PromptInfo<'_>,
|
||||
) -> Cow<'b, str> {
|
||||
if info.line_no() > 0 {
|
||||
CONT_PROMPT.into()
|
||||
} else {
|
||||
prompt.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Validator for ReplState {
|
||||
fn validate(
|
||||
&self,
|
||||
ctx: &mut validate::ValidationContext,
|
||||
) -> rustyline::Result<validate::ValidationResult> {
|
||||
self.validator.validate(ctx)
|
||||
}
|
||||
|
||||
fn validate_while_typing(&self) -> bool {
|
||||
self.validator.validate_while_typing()
|
||||
}
|
||||
}
|
||||
|
||||
fn format_output(
|
||||
opt_output: Option<ReplOutput>,
|
||||
problems: Problems,
|
||||
opt_var_name: Option<String>,
|
||||
dimensions: Option<(usize, usize)>,
|
||||
) -> String {
|
||||
let mut buf = String::new();
|
||||
|
||||
for message in problems.errors.iter().chain(problems.warnings.iter()) {
|
||||
if !buf.is_empty() {
|
||||
buf.push_str("\n\n");
|
||||
}
|
||||
|
||||
buf.push('\n');
|
||||
buf.push_str(message);
|
||||
buf.push('\n');
|
||||
}
|
||||
|
||||
if let Some(ReplOutput { expr, expr_type }) = opt_output {
|
||||
// If expr was empty, it was a type annotation or ability declaration;
|
||||
// don't print anything!
|
||||
//
|
||||
// Also, for now we also don't print anything if there was a compile-time error.
|
||||
// In the future, it would be great to run anyway and print useful output here!
|
||||
if !expr.is_empty() && problems.errors.is_empty() {
|
||||
const EXPR_TYPE_SEPARATOR: &str = " : "; // e.g. in "5 : Num *"
|
||||
|
||||
// Print the expr and its type
|
||||
{
|
||||
buf.push('\n');
|
||||
buf.push_str(&expr);
|
||||
buf.push_str(PINK); // Color for the type separator
|
||||
buf.push_str(EXPR_TYPE_SEPARATOR);
|
||||
buf.push_str(END_COL);
|
||||
buf.push_str(&expr_type);
|
||||
}
|
||||
|
||||
// Print var_name right-aligned on the last line of output.
|
||||
if let Some(var_name) = opt_var_name {
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
const VAR_NAME_PREFIX: &str = " # "; // e.g. in " # val1"
|
||||
const VAR_NAME_COLUMN_MAX: usize = 32; // Right-align the var_name at this column
|
||||
|
||||
let term_width = match dimensions {
|
||||
Some((width, _)) => width.min(VAR_NAME_COLUMN_MAX),
|
||||
None => VAR_NAME_COLUMN_MAX as usize,
|
||||
};
|
||||
|
||||
let expr_with_type = format!("{expr}{EXPR_TYPE_SEPARATOR}{expr_type}");
|
||||
|
||||
// Count graphemes because we care about what's *rendered* in the terminal
|
||||
let last_line_len = expr_with_type
|
||||
.split('\n')
|
||||
.last()
|
||||
.unwrap_or_default()
|
||||
.graphemes(true)
|
||||
.count();
|
||||
let var_name_len =
|
||||
var_name.graphemes(true).count() + VAR_NAME_PREFIX.graphemes(true).count();
|
||||
let spaces_needed = if last_line_len + var_name_len > term_width {
|
||||
buf.push('\n');
|
||||
term_width - var_name_len
|
||||
} else {
|
||||
term_width - last_line_len - var_name_len
|
||||
};
|
||||
|
||||
for _ in 0..spaces_needed {
|
||||
buf.push(' ');
|
||||
}
|
||||
|
||||
buf.push_str(GREEN);
|
||||
buf.push_str(VAR_NAME_PREFIX);
|
||||
buf.push_str(&var_name);
|
||||
buf.push_str(END_COL);
|
||||
buf.push('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf
|
||||
}
|
|
@ -13,7 +13,7 @@ use roc_mono::layout::{
|
|||
self, union_sorted_tags_pub, Builtin, Layout, LayoutCache, LayoutInterner, UnionLayout,
|
||||
UnionVariant, WrappedVariant,
|
||||
};
|
||||
use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral};
|
||||
use roc_parse::ast::{AssignedField, Collection, Expr, Pattern, StrLiteral};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_std::RocDec;
|
||||
use roc_target::TargetInfo;
|
||||
|
@ -29,11 +29,6 @@ struct Env<'a, 'env> {
|
|||
layout_cache: LayoutCache<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ToAstProblem {
|
||||
FunctionLayout,
|
||||
}
|
||||
|
||||
/// JIT execute the given main function, and then wrap its results in an Expr
|
||||
/// so we can display them to the user using the formatter.
|
||||
///
|
||||
|
@ -53,7 +48,7 @@ pub fn jit_to_ast<'a, A: ReplApp<'a>>(
|
|||
interns: &'a Interns,
|
||||
layout_interner: LayoutInterner<'a>,
|
||||
target_info: TargetInfo,
|
||||
) -> Result<Expr<'a>, ToAstProblem> {
|
||||
) -> Expr<'a> {
|
||||
let mut env = Env {
|
||||
arena,
|
||||
subs,
|
||||
|
@ -68,10 +63,24 @@ pub fn jit_to_ast<'a, A: ReplApp<'a>>(
|
|||
result,
|
||||
captures_niche: _,
|
||||
} => {
|
||||
// this is a thunk
|
||||
// This is a thunk, which cannot be defined in userspace, so we know
|
||||
// it's `main` and can be executed.
|
||||
jit_to_ast_help(&mut env, app, main_fn_name, &result, var)
|
||||
}
|
||||
_ => Err(ToAstProblem::FunctionLayout),
|
||||
ProcLayout { arguments, .. } => {
|
||||
// This is a user-supplied function; create a fake Expr for it.
|
||||
let mut arg_patterns =
|
||||
bumpalo::collections::Vec::with_capacity_in(arguments.len(), arena);
|
||||
|
||||
// Put in an underscore for each of the args, just to get the arity right.
|
||||
for _ in 0..arguments.len() {
|
||||
arg_patterns.push(Loc::at_zero(Pattern::Underscore("_")));
|
||||
}
|
||||
|
||||
let body_expr = Loc::at_zero(Expr::Record(Collection::empty()));
|
||||
|
||||
Expr::Closure(arg_patterns.into_bump_slice(), arena.alloc(body_expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -331,7 +340,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
|||
main_fn_name: &str,
|
||||
layout: &Layout<'a>,
|
||||
var: Variable,
|
||||
) -> Result<Expr<'a>, ToAstProblem> {
|
||||
) -> Expr<'a> {
|
||||
let (newtype_containers, alias_content, raw_var) = unroll_newtypes_and_aliases(env, var);
|
||||
|
||||
macro_rules! num_helper {
|
||||
|
@ -342,18 +351,17 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
|||
};
|
||||
}
|
||||
|
||||
let result = match layout {
|
||||
Layout::Builtin(Builtin::Bool) => Ok(app.call_function(
|
||||
main_fn_name,
|
||||
|_mem: &A::Memory, num: bool| {
|
||||
let expr = match layout {
|
||||
Layout::Builtin(Builtin::Bool) => {
|
||||
app.call_function(main_fn_name, |_mem: &A::Memory, num: bool| {
|
||||
bool_to_ast(env, num, env.subs.get_content_without_compacting(raw_var))
|
||||
},
|
||||
)),
|
||||
})
|
||||
}
|
||||
Layout::Builtin(Builtin::Int(int_width)) => {
|
||||
use Content::*;
|
||||
use IntWidth::*;
|
||||
|
||||
let result = match (alias_content, int_width) {
|
||||
match (alias_content, int_width) {
|
||||
(Some(Alias(Symbol::NUM_UNSIGNED8, ..)), U8) => num_helper!(u8),
|
||||
(_, U8) => {
|
||||
// This is not a number, it's a tag union or something else
|
||||
|
@ -371,22 +379,18 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
|||
(_, I32) => num_helper!(i32),
|
||||
(_, I64) => num_helper!(i64),
|
||||
(_, I128) => num_helper!(i128),
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
Layout::Builtin(Builtin::Float(float_width)) => {
|
||||
use FloatWidth::*;
|
||||
|
||||
let result = match float_width {
|
||||
match float_width {
|
||||
F32 => num_helper!(f32),
|
||||
F64 => num_helper!(f64),
|
||||
F128 => todo!("F128 not implemented"),
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
Layout::Builtin(Builtin::Decimal) => Ok(num_helper!(RocDec)),
|
||||
}
|
||||
Layout::Builtin(Builtin::Decimal) => num_helper!(RocDec),
|
||||
Layout::Builtin(Builtin::Str) => {
|
||||
let body = |mem: &A::Memory, addr| {
|
||||
let string = mem.deref_str(addr);
|
||||
|
@ -394,11 +398,9 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
|||
Expr::Str(StrLiteral::PlainLine(arena_str))
|
||||
};
|
||||
|
||||
Ok(app.call_function_returns_roc_str(env.target_info, main_fn_name, body))
|
||||
app.call_function_returns_roc_str(env.target_info, main_fn_name, body)
|
||||
}
|
||||
Layout::Builtin(Builtin::List(elem_layout)) => {
|
||||
//
|
||||
Ok(app.call_function_returns_roc_list(
|
||||
Layout::Builtin(Builtin::List(elem_layout)) => app.call_function_returns_roc_list(
|
||||
main_fn_name,
|
||||
|mem: &A::Memory, (addr, len, _cap)| {
|
||||
list_to_ast(
|
||||
|
@ -410,8 +412,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
|||
env.subs.get_content_without_compacting(raw_var),
|
||||
)
|
||||
},
|
||||
))
|
||||
}
|
||||
),
|
||||
Layout::Struct { field_layouts, .. } => {
|
||||
let fields = [Layout::u64(), *layout];
|
||||
let layout = Layout::struct_no_name_order(env.arena.alloc(fields));
|
||||
|
@ -423,38 +424,24 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
|||
.get_content_without_compacting(raw_var)
|
||||
{
|
||||
Content::Structure(FlatType::Record(fields, _)) => {
|
||||
Ok(struct_to_ast(env, mem, addr, *fields))
|
||||
struct_to_ast(env, mem, addr, *fields)
|
||||
}
|
||||
Content::Structure(FlatType::EmptyRecord) => {
|
||||
Ok(struct_to_ast(env, mem, addr, RecordFields::empty()))
|
||||
struct_to_ast(env, mem, addr, RecordFields::empty())
|
||||
}
|
||||
Content::Structure(FlatType::TagUnion(tags, _)) => {
|
||||
let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags);
|
||||
|
||||
Ok(single_tag_union_to_ast(
|
||||
env,
|
||||
mem,
|
||||
addr,
|
||||
field_layouts,
|
||||
tag_name,
|
||||
payload_vars,
|
||||
))
|
||||
single_tag_union_to_ast(env, mem, addr, field_layouts, tag_name, payload_vars)
|
||||
}
|
||||
Content::Structure(FlatType::FunctionOrTagUnion(tag_names, _, _)) => {
|
||||
let tag_name = &env.subs.get_subs_slice(*tag_names)[0];
|
||||
|
||||
Ok(single_tag_union_to_ast(
|
||||
env,
|
||||
mem,
|
||||
addr,
|
||||
field_layouts,
|
||||
tag_name,
|
||||
&[],
|
||||
))
|
||||
single_tag_union_to_ast(env, mem, addr, field_layouts, tag_name, &[])
|
||||
}
|
||||
Content::Structure(FlatType::Func(_, _, _)) => {
|
||||
// a function with a struct as the closure environment
|
||||
Ok(OPAQUE_FUNCTION)
|
||||
OPAQUE_FUNCTION
|
||||
}
|
||||
other => {
|
||||
unreachable!(
|
||||
|
@ -472,7 +459,8 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
|||
}
|
||||
Layout::Union(UnionLayout::NonRecursive(_)) => {
|
||||
let size = layout.stack_size(&env.layout_cache.interner, env.target_info);
|
||||
Ok(app.call_function_dynamic_size(
|
||||
|
||||
app.call_function_dynamic_size(
|
||||
main_fn_name,
|
||||
size as usize,
|
||||
|mem: &'a A::Memory, addr: usize| {
|
||||
|
@ -485,14 +473,15 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
|||
env.subs.get_root_key_without_compacting(raw_var),
|
||||
)
|
||||
},
|
||||
))
|
||||
)
|
||||
}
|
||||
Layout::Union(UnionLayout::Recursive(_))
|
||||
| Layout::Union(UnionLayout::NonNullableUnwrapped(_))
|
||||
| Layout::Union(UnionLayout::NullableUnwrapped { .. })
|
||||
| Layout::Union(UnionLayout::NullableWrapped { .. }) => {
|
||||
let size = layout.stack_size(&env.layout_cache.interner, env.target_info);
|
||||
Ok(app.call_function_dynamic_size(
|
||||
|
||||
app.call_function_dynamic_size(
|
||||
main_fn_name,
|
||||
size as usize,
|
||||
|mem: &'a A::Memory, addr: usize| {
|
||||
|
@ -505,18 +494,16 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
|||
env.subs.get_root_key_without_compacting(raw_var),
|
||||
)
|
||||
},
|
||||
))
|
||||
)
|
||||
}
|
||||
Layout::RecursivePointer => {
|
||||
unreachable!("RecursivePointers can only be inside structures")
|
||||
}
|
||||
Layout::LambdaSet(_) => Ok(OPAQUE_FUNCTION),
|
||||
Layout::LambdaSet(_) => OPAQUE_FUNCTION,
|
||||
Layout::Boxed(_) => {
|
||||
let size = layout.stack_size(&env.layout_cache.interner, env.target_info);
|
||||
Ok(app.call_function_dynamic_size(
|
||||
main_fn_name,
|
||||
size as usize,
|
||||
|mem: &A::Memory, addr| {
|
||||
|
||||
app.call_function_dynamic_size(main_fn_name, size as usize, |mem: &A::Memory, addr| {
|
||||
addr_to_ast(
|
||||
env,
|
||||
mem,
|
||||
|
@ -525,11 +512,11 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
|||
WhenRecursive::Unreachable,
|
||||
env.subs.get_root_key_without_compacting(raw_var),
|
||||
)
|
||||
},
|
||||
))
|
||||
})
|
||||
}
|
||||
};
|
||||
result.map(|e| apply_newtypes(env, newtype_containers.into_bump_slice(), e))
|
||||
|
||||
apply_newtypes(env, newtype_containers.into_bump_slice(), expr)
|
||||
}
|
||||
|
||||
fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use bumpalo::Bump;
|
||||
use roc_load::{ExecutionMode, LoadConfig, Threading};
|
||||
use roc_reporting::report::Palette;
|
||||
use roc_reporting::report::{Palette, Severity};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use roc_fmt::annotation::Formattable;
|
||||
|
@ -11,48 +11,47 @@ use roc_region::all::LineInfo;
|
|||
use roc_reporting::report::{can_problem, type_problem, RocDocAllocator};
|
||||
use roc_target::TargetInfo;
|
||||
|
||||
use crate::eval::ToAstProblem;
|
||||
|
||||
pub enum ReplOutput {
|
||||
Problems(Vec<String>),
|
||||
NoProblems { expr: String, expr_type: String },
|
||||
#[derive(Debug)]
|
||||
pub struct ReplOutput {
|
||||
pub expr: String,
|
||||
pub expr_type: String,
|
||||
}
|
||||
|
||||
pub fn format_answer(
|
||||
arena: &Bump,
|
||||
res_answer: Result<Expr, ToAstProblem>,
|
||||
expr_type_str: String,
|
||||
) -> ReplOutput {
|
||||
pub fn format_answer<'a>(arena: &'a Bump, answer: Expr<'_>) -> &'a str {
|
||||
match answer {
|
||||
Expr::Closure(_, _) | Expr::MalformedClosure => "<function>",
|
||||
_ => {
|
||||
let mut expr = roc_fmt::Buf::new_in(arena);
|
||||
|
||||
use ToAstProblem::*;
|
||||
match res_answer {
|
||||
Ok(answer) => {
|
||||
answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0);
|
||||
}
|
||||
Err(FunctionLayout) => {
|
||||
expr.indent(0);
|
||||
expr.push_str("<function>");
|
||||
}
|
||||
}
|
||||
|
||||
ReplOutput::NoProblems {
|
||||
expr: expr.into_bump_str().to_string(),
|
||||
expr_type: expr_type_str,
|
||||
expr.into_bump_str()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile_to_mono<'a>(
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Problems {
|
||||
pub errors: Vec<String>,
|
||||
pub warnings: Vec<String>,
|
||||
}
|
||||
|
||||
impl Problems {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.errors.is_empty() && self.warnings.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile_to_mono<'a, 'i, I: Iterator<Item = &'i str>>(
|
||||
arena: &'a Bump,
|
||||
src: &str,
|
||||
defs: I,
|
||||
expr: &str,
|
||||
target_info: TargetInfo,
|
||||
palette: Palette,
|
||||
) -> Result<MonomorphizedModule<'a>, Vec<String>> {
|
||||
) -> (Option<MonomorphizedModule<'a>>, Problems) {
|
||||
let filename = PathBuf::from("");
|
||||
let src_dir = PathBuf::from("fake/test/path");
|
||||
|
||||
let module_src = arena.alloc(promote_expr_to_module(src));
|
||||
|
||||
let (bytes_before_expr, module_src) = promote_expr_to_module(arena, defs, expr);
|
||||
let exposed_types = Default::default();
|
||||
let loaded = roc_load::load_and_monomorphize_from_str(
|
||||
arena,
|
||||
|
@ -71,10 +70,16 @@ pub fn compile_to_mono<'a>(
|
|||
let mut loaded = match loaded {
|
||||
Ok(v) => v,
|
||||
Err(LoadingProblem::FormattedReport(report)) => {
|
||||
return Err(vec![report]);
|
||||
return (
|
||||
None,
|
||||
Problems {
|
||||
errors: vec![report],
|
||||
warnings: Vec::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("error while loading module: {:?}", e)
|
||||
todo!("error while loading module: {:?}", e)
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -86,7 +91,10 @@ pub fn compile_to_mono<'a>(
|
|||
..
|
||||
} = &mut loaded;
|
||||
|
||||
let mut lines = Vec::new();
|
||||
let mut problems = Problems::default();
|
||||
|
||||
let errors = &mut problems.errors;
|
||||
let warnings = &mut problems.warnings;
|
||||
|
||||
for (home, (module_path, src)) in sources.iter() {
|
||||
let can_probs = can_problems.remove(home).unwrap_or_default();
|
||||
|
@ -105,42 +113,77 @@ pub fn compile_to_mono<'a>(
|
|||
let alloc = RocDocAllocator::new(&src_lines, *home, interns);
|
||||
|
||||
for problem in can_probs.into_iter() {
|
||||
// Filter out all warnings and errors whose regions end before this,
|
||||
// because they must be part of the defs (excluding the most renently added def,
|
||||
// if that's the one being evaluated) and therefore not things we should show.
|
||||
// This filters out things like shadowing warnings and unused def warnings.
|
||||
if problem.region().unwrap_or_default().end().offset as usize >= bytes_before_expr {
|
||||
let report = can_problem(&alloc, &line_info, module_path.clone(), problem);
|
||||
let severity = report.severity;
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
match severity {
|
||||
Severity::Warning => {
|
||||
warnings.push(buf);
|
||||
}
|
||||
Severity::RuntimeError => {
|
||||
errors.push(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for problem in type_probs {
|
||||
if let Some(report) = type_problem(&alloc, &line_info, module_path.clone(), problem) {
|
||||
let severity = report.severity;
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
match severity {
|
||||
Severity::Warning => {
|
||||
warnings.push(buf);
|
||||
}
|
||||
Severity::RuntimeError => {
|
||||
errors.push(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !lines.is_empty() {
|
||||
Err(lines)
|
||||
} else {
|
||||
Ok(loaded)
|
||||
}
|
||||
(Some(loaded), problems)
|
||||
}
|
||||
|
||||
fn promote_expr_to_module(src: &str) -> String {
|
||||
let mut buffer =
|
||||
String::from("app \"app\" provides [replOutput] to \"./platform\"\n\nreplOutput =\n");
|
||||
fn promote_expr_to_module<'a, 'i, I: Iterator<Item = &'i str>>(
|
||||
arena: &'a Bump,
|
||||
defs: I,
|
||||
expr: &str,
|
||||
) -> (usize, &'a str) {
|
||||
const REPL_MODULE_HEADER: &str = "app \"app\" provides [replOutput] to \"./platform\"\n\n";
|
||||
const REPL_MODULE_MAIN_DEF: &str = "replOutput =\n";
|
||||
const INDENT: &str = " ";
|
||||
|
||||
for line in src.lines() {
|
||||
// indent the body!
|
||||
buffer.push_str(" ");
|
||||
let mut buffer = bumpalo::collections::string::String::from_str_in(REPL_MODULE_HEADER, arena);
|
||||
|
||||
for line in defs {
|
||||
// don't indent the defs
|
||||
buffer.push_str(line);
|
||||
buffer.push_str("\n\n");
|
||||
}
|
||||
|
||||
buffer.push_str(REPL_MODULE_MAIN_DEF);
|
||||
|
||||
let bytes_before_expr = buffer.len();
|
||||
|
||||
for line in expr.lines() {
|
||||
// indent the expr!
|
||||
buffer.push_str(INDENT);
|
||||
buffer.push_str(line);
|
||||
buffer.push('\n');
|
||||
}
|
||||
|
||||
buffer
|
||||
(bytes_before_expr, buffer.into_bump_str())
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ roc_types = {path = "../compiler/types"}
|
|||
roc_gen_llvm = {path = "../compiler/gen_llvm"}
|
||||
roc_region = { path = "../compiler/region" }
|
||||
roc_build = { path = "../compiler/build" }
|
||||
roc_error_macros = { path = "../error_macros" }
|
||||
|
||||
libloading = "0.7.1"
|
||||
inkwell = { path = "../vendor/inkwell" }
|
||||
|
|
|
@ -8,10 +8,7 @@ use {
|
|||
layout::{CapturesNiche, Layout, LayoutCache},
|
||||
},
|
||||
roc_parse::ast::Expr,
|
||||
roc_repl_eval::{
|
||||
eval::{jit_to_ast, ToAstProblem},
|
||||
ReplAppMemory,
|
||||
},
|
||||
roc_repl_eval::{eval::jit_to_ast, ReplAppMemory},
|
||||
roc_target::TargetInfo,
|
||||
roc_types::subs::{Subs, Variable},
|
||||
std::sync::Arc,
|
||||
|
@ -36,7 +33,7 @@ pub fn get_values<'a>(
|
|||
start: *const u8,
|
||||
start_offset: usize,
|
||||
variables: &[Variable],
|
||||
) -> Result<(usize, Vec<Expr<'a>>), ToAstProblem> {
|
||||
) -> (usize, Vec<Expr<'a>>) {
|
||||
let mut result = Vec::with_capacity(variables.len());
|
||||
|
||||
let memory = ExpectMemory { start };
|
||||
|
@ -75,13 +72,13 @@ pub fn get_values<'a>(
|
|||
interns,
|
||||
layout_interner.fork(),
|
||||
target_info,
|
||||
)?
|
||||
)
|
||||
};
|
||||
|
||||
result.push(expr);
|
||||
}
|
||||
|
||||
Ok((app.offset, result))
|
||||
(app.offset, result)
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
|
|
|
@ -6,6 +6,7 @@ use inkwell::context::Context;
|
|||
use roc_build::link::llvm_module_to_dylib;
|
||||
use roc_can::expr::ExpectLookup;
|
||||
use roc_collections::{MutSet, VecMap};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_gen_llvm::{
|
||||
llvm::{build::LlvmBackendMode, externs::add_default_roc_externs},
|
||||
run_roc::RocCallResult,
|
||||
|
@ -21,7 +22,7 @@ use roc_target::TargetInfo;
|
|||
use roc_types::subs::{Subs, Variable};
|
||||
use target_lexicon::Triple;
|
||||
|
||||
pub(crate) struct ExpectMemory<'a> {
|
||||
pub struct ExpectMemory<'a> {
|
||||
ptr: *mut u8,
|
||||
length: usize,
|
||||
shm_name: Option<std::ffi::CString>,
|
||||
|
@ -41,7 +42,7 @@ impl<'a> ExpectMemory<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn create_or_reuse_mmap(shm_name: &str) -> Self {
|
||||
pub fn create_or_reuse_mmap(shm_name: &str) -> Self {
|
||||
let cstring = std::ffi::CString::new(shm_name).unwrap();
|
||||
Self::mmap_help(cstring, libc::O_RDWR | libc::O_CREAT)
|
||||
}
|
||||
|
@ -54,19 +55,38 @@ impl<'a> ExpectMemory<'a> {
|
|||
fn mmap_help(cstring: std::ffi::CString, shm_flags: i32) -> Self {
|
||||
let ptr = unsafe {
|
||||
let shared_fd = libc::shm_open(cstring.as_ptr().cast(), shm_flags, 0o666);
|
||||
if shared_fd == -1 {
|
||||
internal_error!("failed to shm_open fd");
|
||||
}
|
||||
|
||||
libc::ftruncate(shared_fd, Self::SHM_SIZE as _);
|
||||
// NOTE: we can only call `ftruncate` once on this file descriptor on mac
|
||||
if libc::ftruncate(shared_fd, Self::SHM_SIZE as _) == -1 {
|
||||
internal_error!("failed to truncate shared file, are the permissions wrong?");
|
||||
}
|
||||
|
||||
libc::mmap(
|
||||
let ptr = libc::mmap(
|
||||
std::ptr::null_mut(),
|
||||
Self::SHM_SIZE,
|
||||
libc::PROT_WRITE | libc::PROT_READ,
|
||||
libc::MAP_SHARED,
|
||||
shared_fd,
|
||||
0,
|
||||
)
|
||||
);
|
||||
|
||||
if ptr as usize == usize::MAX {
|
||||
// ptr = -1
|
||||
roc_error_macros::internal_error!("failed to mmap shared pointer")
|
||||
}
|
||||
|
||||
// fill the buffer with a fill pattern
|
||||
libc::memset(ptr, 0xAA, Self::SHM_SIZE);
|
||||
|
||||
ptr
|
||||
};
|
||||
|
||||
// puts in the initial header
|
||||
let _ = ExpectSequence::new(ptr as *mut u8);
|
||||
|
||||
Self {
|
||||
ptr: ptr.cast(),
|
||||
length: Self::SHM_SIZE,
|
||||
|
@ -83,7 +103,34 @@ impl<'a> ExpectMemory<'a> {
|
|||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn run_expects<'a, W: std::io::Write>(
|
||||
pub fn run_inline_expects<'a, W: std::io::Write>(
|
||||
writer: &mut W,
|
||||
render_target: RenderTarget,
|
||||
arena: &'a Bump,
|
||||
interns: &'a Interns,
|
||||
layout_interner: &Arc<GlobalInterner<'a, Layout<'a>>>,
|
||||
lib: &libloading::Library,
|
||||
expectations: &mut VecMap<ModuleId, Expectations>,
|
||||
expects: ExpectFunctions<'_>,
|
||||
) -> std::io::Result<(usize, usize)> {
|
||||
let shm_name = format!("/roc_expect_buffer_{}", std::process::id());
|
||||
let mut memory = ExpectMemory::create_or_reuse_mmap(&shm_name);
|
||||
|
||||
run_expects_with_memory(
|
||||
writer,
|
||||
render_target,
|
||||
arena,
|
||||
interns,
|
||||
layout_interner,
|
||||
lib,
|
||||
expectations,
|
||||
expects,
|
||||
&mut memory,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn run_toplevel_expects<'a, W: std::io::Write>(
|
||||
writer: &mut W,
|
||||
render_target: RenderTarget,
|
||||
arena: &'a Bump,
|
||||
|
@ -325,14 +372,16 @@ fn run_expect_fx<'a, W: std::io::Write>(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn roc_dev_expect<'a>(
|
||||
pub fn render_expects_in_memory<'a>(
|
||||
writer: &mut impl std::io::Write,
|
||||
arena: &'a Bump,
|
||||
expectations: &mut VecMap<ModuleId, Expectations>,
|
||||
interns: &'a Interns,
|
||||
layout_interner: &Arc<GlobalInterner<'a, Layout<'a>>>,
|
||||
shared_ptr: *mut u8,
|
||||
memory: &ExpectMemory,
|
||||
) -> std::io::Result<usize> {
|
||||
let shared_ptr = memory.ptr;
|
||||
|
||||
let frame = ExpectFrame::at_offset(shared_ptr, ExpectSequence::START_OFFSET);
|
||||
let module_id = frame.module_id;
|
||||
|
||||
|
@ -423,8 +472,7 @@ fn render_expect_failure<'a>(
|
|||
start,
|
||||
frame.start_offset,
|
||||
&variables,
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
|
||||
renderer.render_failure(
|
||||
writer,
|
||||
|
|
|
@ -17,6 +17,7 @@ indoc = "1.0.7"
|
|||
strip-ansi-escapes = "0.1.1"
|
||||
wasmer-wasi = "2.2.1"
|
||||
|
||||
roc_build = { path = "../compiler/build" }
|
||||
roc_repl_cli = {path = "../repl_cli"}
|
||||
roc_test_utils = {path = "../test_utils"}
|
||||
|
||||
|
@ -28,4 +29,18 @@ wasmer = { version = "2.2.1", default-features = false, features = ["singlepass"
|
|||
wasmer = { version = "2.2.1", default-features = false, features = ["cranelift", "universal"] }
|
||||
|
||||
[features]
|
||||
wasm = []
|
||||
default = ["target-aarch64", "target-x86_64", "target-wasm32"]
|
||||
wasm = ["target-wasm32"]
|
||||
target-arm = ["roc_build/target-arm", "roc_repl_cli/target-arm"]
|
||||
target-aarch64 = ["roc_build/target-aarch64", "roc_repl_cli/target-aarch64"]
|
||||
target-x86 = ["roc_build/target-x86", "roc_repl_cli/target-x86"]
|
||||
target-x86_64 = ["roc_build/target-x86_64", "roc_repl_cli/target-x86_64"]
|
||||
target-wasm32 = ["roc_build/target-wasm32", "roc_repl_cli/target-wasm32"]
|
||||
|
||||
target-all = [
|
||||
"target-aarch64",
|
||||
"target-arm",
|
||||
"target-x86",
|
||||
"target-x86_64",
|
||||
"target-wasm32"
|
||||
]
|
||||
|
|
|
@ -3,16 +3,16 @@ use std::io::Write;
|
|||
use std::path::PathBuf;
|
||||
use std::process::{Command, ExitStatus, Stdio};
|
||||
|
||||
use roc_repl_cli::{TIPS, WELCOME_MESSAGE};
|
||||
use roc_repl_cli::{SHORT_INSTRUCTIONS, WELCOME_MESSAGE};
|
||||
use roc_test_utils::assert_multiline_str_eq;
|
||||
|
||||
const ERROR_MESSAGE_START: char = '─';
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Out {
|
||||
stdout: String,
|
||||
stderr: String,
|
||||
status: ExitStatus,
|
||||
pub struct Out {
|
||||
pub stdout: String,
|
||||
pub stderr: String,
|
||||
pub status: ExitStatus,
|
||||
}
|
||||
|
||||
fn path_to_roc_binary() -> PathBuf {
|
||||
|
@ -39,7 +39,7 @@ fn path_to_roc_binary() -> PathBuf {
|
|||
path
|
||||
}
|
||||
|
||||
fn repl_eval(input: &str) -> Out {
|
||||
pub fn repl_eval(input: &str) -> Out {
|
||||
let mut cmd = Command::new(path_to_roc_binary());
|
||||
|
||||
cmd.arg("repl");
|
||||
|
@ -75,7 +75,7 @@ fn repl_eval(input: &str) -> Out {
|
|||
|
||||
// Remove the initial instructions from the output.
|
||||
|
||||
let expected_instructions = format!("{}{}", WELCOME_MESSAGE, TIPS);
|
||||
let expected_instructions = format!("{}{}", WELCOME_MESSAGE, SHORT_INSTRUCTIONS);
|
||||
let stdout = String::from_utf8(output.stdout).unwrap();
|
||||
|
||||
assert!(
|
||||
|
@ -124,10 +124,25 @@ fn repl_eval(input: &str) -> Out {
|
|||
}
|
||||
|
||||
pub fn expect_success(input: &str, expected: &str) {
|
||||
let out = repl_eval(input);
|
||||
let out = repl_eval(input.trim());
|
||||
|
||||
assert_multiline_str_eq!("", out.stderr.as_str());
|
||||
assert_multiline_str_eq!(expected, out.stdout.as_str());
|
||||
|
||||
// Don't consider the auto variable name (e.g. "# val1") at the end.
|
||||
// The state.rs tests do that!
|
||||
let mut iter = out.stdout.lines().rev();
|
||||
let line = iter.next().unwrap();
|
||||
let comment_index = line.rfind('#').unwrap_or(line.len());
|
||||
let line_without_comment = line[0..comment_index].trim_end();
|
||||
|
||||
// Sometimes the "# val1" wraps around to its own line; if this happens,
|
||||
// we just use the preceding line instead.
|
||||
if line_without_comment.is_empty() {
|
||||
assert_multiline_str_eq!(expected, iter.next().unwrap().trim_end());
|
||||
} else {
|
||||
assert_multiline_str_eq!(expected, line_without_comment);
|
||||
}
|
||||
|
||||
assert!(out.status.success());
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,9 @@ extern crate lazy_static;
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod state;
|
||||
|
||||
#[cfg(all(test, not(feature = "wasm")))]
|
||||
mod cli;
|
||||
|
||||
|
|
152
crates/repl_test/src/state.rs
Normal file
152
crates/repl_test/src/state.rs
Normal file
|
@ -0,0 +1,152 @@
|
|||
use indoc::indoc;
|
||||
use roc_repl_cli::repl_state::{is_incomplete, ReplState, TIPS};
|
||||
|
||||
// These are tests of the REPL state machine. They work without actually
|
||||
// running the CLI, and without using rustyline, and instead verify
|
||||
// the expected outputs for various sequences of user input strings.
|
||||
|
||||
#[test]
|
||||
fn one_plus_one() {
|
||||
complete("1 + 1", &mut ReplState::new(), Ok(("2 : Num *", "val1")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generated_expr_names() {
|
||||
let mut state = ReplState::new();
|
||||
|
||||
complete("2 * 3", &mut state, Ok(("6 : Num *", "val1")));
|
||||
complete("4 - 1", &mut state, Ok(("3 : Num *", "val2")));
|
||||
complete("val1 + val2", &mut state, Ok(("9 : Num *", "val3")));
|
||||
complete("1 + (val2 * val3)", &mut state, Ok(("28 : Num *", "val4")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persisted_defs() {
|
||||
let mut state = ReplState::new();
|
||||
|
||||
complete("x = 5", &mut state, Ok(("5 : Num *", "x")));
|
||||
complete("7 - 3", &mut state, Ok(("4 : Num *", "val1")));
|
||||
complete("y = 6", &mut state, Ok(("6 : Num *", "y")));
|
||||
complete("val1 + x + y", &mut state, Ok(("15 : Num *", "val2")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn annotated_body() {
|
||||
let mut input = "t : [A, B, C]".to_string();
|
||||
|
||||
incomplete(&mut input);
|
||||
|
||||
input.push_str("t = A");
|
||||
|
||||
complete(&input, &mut ReplState::new(), Ok(("A : [A, B, C]", "t")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exhaustiveness_problem() {
|
||||
let mut state = ReplState::new();
|
||||
|
||||
// Enter an annotated tag union to make it exhaustive
|
||||
{
|
||||
let mut input = "t : [A, B, C]".to_string();
|
||||
|
||||
incomplete(&mut input);
|
||||
|
||||
input.push_str("t = A");
|
||||
|
||||
complete(&input, &mut state, Ok(("A : [A, B, C]", "t")));
|
||||
}
|
||||
|
||||
// Run a `when` on it that isn't exhaustive
|
||||
{
|
||||
let mut input = "when t is".to_string();
|
||||
incomplete(&mut input);
|
||||
|
||||
input.push_str(" A -> 1");
|
||||
incomplete(&mut input);
|
||||
|
||||
const EXPECTED_ERROR: &str = indoc!(
|
||||
r#"
|
||||
── UNSAFE PATTERN ──────────────────────────────────────────────────────────────
|
||||
|
||||
This when does not cover all the possibilities:
|
||||
|
||||
7│> when t is
|
||||
8│> A -> 1
|
||||
|
||||
Other possibilities include:
|
||||
|
||||
B
|
||||
C
|
||||
|
||||
I would have to crash if I saw one of those! Add branches for them!"#
|
||||
);
|
||||
|
||||
error(&input, &mut state, EXPECTED_ERROR.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tips() {
|
||||
assert!(!is_incomplete(""));
|
||||
assert_eq!(ReplState::new().step("", None), Ok(TIPS.to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn standalone_annotation() {
|
||||
let mut state = ReplState::new();
|
||||
let mut input = "x : Str".to_string();
|
||||
|
||||
incomplete(&mut input);
|
||||
assert!(!is_incomplete(&input));
|
||||
assert_eq!(state.step(&input, None), Ok(String::new()));
|
||||
}
|
||||
|
||||
/// validate and step the given input, then check the Result vs the output
|
||||
/// with ANSI escape codes stripped.
|
||||
fn complete(input: &str, state: &mut ReplState, expected_step_result: Result<(&str, &str), i32>) {
|
||||
assert!(!is_incomplete(input));
|
||||
|
||||
match state.step(input, None) {
|
||||
Ok(string) => {
|
||||
let escaped =
|
||||
std::string::String::from_utf8(strip_ansi_escapes::strip(string.trim()).unwrap())
|
||||
.unwrap();
|
||||
|
||||
let comment_index = escaped.rfind('#').unwrap_or(escaped.len());
|
||||
|
||||
assert_eq!(
|
||||
expected_step_result.map(|(starts_with, _)| starts_with),
|
||||
Ok(escaped[0..comment_index].trim())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
expected_step_result.map(|(_, ends_with)| ends_with),
|
||||
// +1 because we want to skip over the '#' itself
|
||||
Ok(escaped[comment_index + 1..].trim())
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
assert_eq!(expected_step_result, Err(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn incomplete(input: &mut String) {
|
||||
assert!(is_incomplete(input));
|
||||
|
||||
// Since this was incomplete, rustyline won't step the state. Instead, it will
|
||||
// remember the input (with a newline appended) for next time.
|
||||
input.push('\n');
|
||||
}
|
||||
|
||||
/// validate and step the given input, then check the given string vs the output
|
||||
/// with ANSI escape codes stripped.
|
||||
fn error(input: &str, state: &mut ReplState, expected_step_result: String) {
|
||||
assert!(!is_incomplete(input));
|
||||
|
||||
let escaped = state.step(input, None).map(|string| {
|
||||
std::string::String::from_utf8(strip_ansi_escapes::strip(string.trim()).unwrap()).unwrap()
|
||||
});
|
||||
|
||||
assert_eq!(Ok(expected_step_result), escaped);
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
#[allow(unused_imports)]
|
||||
use indoc::indoc;
|
||||
use roc_test_utils::assert_multiline_str_eq;
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
use crate::cli::{expect_failure, expect_success};
|
||||
use crate::cli::{expect_failure, expect_success, repl_eval};
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
#[allow(unused_imports)]
|
||||
|
@ -199,8 +200,7 @@ fn newtype_of_big_data() {
|
|||
Either a b : [Left a, Right b]
|
||||
lefty : Either Str Str
|
||||
lefty = Left "loosey"
|
||||
A lefty
|
||||
"#
|
||||
A lefty"#
|
||||
),
|
||||
r#"A (Left "loosey") : [A (Either Str Str)]"#,
|
||||
)
|
||||
|
@ -214,8 +214,7 @@ fn newtype_nested() {
|
|||
Either a b : [Left a, Right b]
|
||||
lefty : Either Str Str
|
||||
lefty = Left "loosey"
|
||||
A (B (C lefty))
|
||||
"#
|
||||
A (B (C lefty))"#
|
||||
),
|
||||
r#"A (B (C (Left "loosey"))) : [A [B [C (Either Str Str)]]]"#,
|
||||
)
|
||||
|
@ -229,8 +228,7 @@ fn newtype_of_big_of_newtype() {
|
|||
Big a : [Big a [Wrapper [Newtype a]]]
|
||||
big : Big Str
|
||||
big = Big "s" (Wrapper (Newtype "t"))
|
||||
A big
|
||||
"#
|
||||
A big"#
|
||||
),
|
||||
r#"A (Big "s" (Wrapper (Newtype "t"))) : [A (Big Str)]"#,
|
||||
)
|
||||
|
@ -560,8 +558,36 @@ fn four_element_record() {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn multiline_string() {
|
||||
fn multiline_string_non_wasm() {
|
||||
// If a string contains newlines, format it as a multiline string in the output.
|
||||
|
||||
// We can't use expect_success to test this, because it only looks at the last
|
||||
// line of output, and in this case we care about every line of output!
|
||||
let out = repl_eval(r#""\n\nhi!\n\n""#);
|
||||
let expected = indoc!(
|
||||
r#""""
|
||||
|
||||
|
||||
hi!
|
||||
|
||||
|
||||
""" : Str"#
|
||||
);
|
||||
|
||||
assert_multiline_str_eq!("", out.stderr.as_str());
|
||||
|
||||
// Don't consider the auto variable name ("# val1") at the end.
|
||||
// The state.rs tests do that!
|
||||
assert_multiline_str_eq!(expected, out.stdout.replace("# val1", "").trim());
|
||||
|
||||
assert!(out.status.success());
|
||||
}
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
#[test]
|
||||
fn multiline_string_wasm() {
|
||||
// If a string contains newlines, format it as a multiline string in the output
|
||||
expect_success(
|
||||
r#""\n\nhi!\n\n""#,
|
||||
|
@ -933,39 +959,7 @@ fn parse_problem() {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))] // TODO: mismatch is due to terminal control codes!
|
||||
#[test]
|
||||
fn exhaustiveness_problem() {
|
||||
expect_failure(
|
||||
indoc!(
|
||||
r#"
|
||||
t : [A, B, C]
|
||||
t = A
|
||||
|
||||
when t is
|
||||
A -> "a"
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── UNSAFE PATTERN ──────────────────────────────────────────────────────────────
|
||||
|
||||
This when does not cover all the possibilities:
|
||||
|
||||
7│> when t is
|
||||
8│> A -> "a"
|
||||
|
||||
Other possibilities include:
|
||||
|
||||
B
|
||||
C
|
||||
|
||||
I would have to crash if I saw one of those! Add branches for them!
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[ignore] // re-enable (and fix) after https://github.com/roc-lang/roc/issues/4425 is done!
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn issue_2343_complete_mono_with_shadowed_vars() {
|
||||
|
@ -1024,8 +1018,7 @@ fn tag_with_type_behind_alias() {
|
|||
T : [A Str]
|
||||
v : T
|
||||
v = A "value"
|
||||
v
|
||||
"#
|
||||
v"#
|
||||
),
|
||||
r#"A "value" : T"#,
|
||||
);
|
||||
|
@ -1039,8 +1032,7 @@ fn issue_2588_record_with_function_and_nonfunction() {
|
|||
r#"
|
||||
x = 1
|
||||
f = \n -> n * 2
|
||||
{ y: f x, f }
|
||||
"#
|
||||
{ y: f x, f }"#
|
||||
),
|
||||
r#"{ f: <function>, y: 2 } : { f : Num a -> Num a, y : Num * }"#,
|
||||
)
|
||||
|
@ -1053,8 +1045,7 @@ fn opaque_apply() {
|
|||
r#"
|
||||
Age := U32
|
||||
|
||||
@Age 23
|
||||
"#
|
||||
@Age 23"#
|
||||
),
|
||||
"@Age 23 : Age",
|
||||
)
|
||||
|
@ -1067,8 +1058,7 @@ fn opaque_apply_polymorphic() {
|
|||
r#"
|
||||
F t u := [Package t u]
|
||||
|
||||
@F (Package "" { a: "" })
|
||||
"#
|
||||
@F (Package "" { a: "" })"#
|
||||
),
|
||||
r#"@F (Package "" { a: "" }) : F Str { a : Str }"#,
|
||||
)
|
||||
|
@ -1083,8 +1073,7 @@ fn opaque_pattern_and_call() {
|
|||
|
||||
f = \@F (Package A {}) -> @F (Package {} A)
|
||||
|
||||
f (@F (Package A {}))
|
||||
"#
|
||||
f (@F (Package A {}))"#
|
||||
),
|
||||
r#"@F (Package {} A) : F {} [A]"#,
|
||||
)
|
||||
|
@ -1097,10 +1086,9 @@ fn dec_in_repl() {
|
|||
r#"
|
||||
x: Dec
|
||||
x=1.23
|
||||
x
|
||||
"#
|
||||
x"#
|
||||
),
|
||||
r#"1.23 : Dec"#,
|
||||
"1.23 : Dec",
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1111,8 +1099,7 @@ fn print_i8_issue_2710() {
|
|||
r#"
|
||||
a : I8
|
||||
a = -1
|
||||
a
|
||||
"#
|
||||
a"#
|
||||
),
|
||||
r#"-1 : I8"#,
|
||||
)
|
||||
|
@ -1124,8 +1111,7 @@ fn box_box() {
|
|||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
Box.box "container store"
|
||||
"#
|
||||
Box.box "container store""#
|
||||
),
|
||||
r#"Box.box "container store" : Box Str"#,
|
||||
)
|
||||
|
@ -1140,8 +1126,7 @@ fn box_box_type_alias() {
|
|||
HeapStr : Box Str
|
||||
helloHeap : HeapStr
|
||||
helloHeap = Box.box "bye stacks"
|
||||
helloHeap
|
||||
"#
|
||||
helloHeap"#
|
||||
),
|
||||
r#"Box.box "bye stacks" : HeapStr"#,
|
||||
)
|
||||
|
@ -1165,9 +1150,7 @@ fn issue_2818() {
|
|||
f : {} -> List Str
|
||||
f = \_ ->
|
||||
x = []
|
||||
x
|
||||
f
|
||||
"#
|
||||
x"#
|
||||
),
|
||||
r"<function> : {} -> List Str",
|
||||
)
|
||||
|
@ -1186,8 +1169,7 @@ fn issue_2810_recursive_layout_inside_nonrecursive() {
|
|||
|
||||
a : Job
|
||||
a = Job (Command (FromJob (Job (Command SystemTool))))
|
||||
a
|
||||
"#
|
||||
a"#
|
||||
),
|
||||
"Job (Command (FromJob (Job (Command SystemTool)))) : Job",
|
||||
)
|
||||
|
@ -1199,14 +1181,10 @@ fn render_nullable_unwrapped_passing_through_alias() {
|
|||
indoc!(
|
||||
r#"
|
||||
Deep : [L DeepList]
|
||||
|
||||
DeepList : [Nil, Cons Deep]
|
||||
|
||||
v : DeepList
|
||||
v = (Cons (L (Cons (L (Cons (L Nil))))))
|
||||
|
||||
v
|
||||
"#
|
||||
v"#
|
||||
),
|
||||
"Cons (L (Cons (L (Cons (L Nil))))) : DeepList",
|
||||
)
|
||||
|
@ -1218,8 +1196,7 @@ fn opaque_wrap_function() {
|
|||
indoc!(
|
||||
r#"
|
||||
A a := a
|
||||
List.map [1u8, 2u8, 3u8] @A
|
||||
"#
|
||||
List.map [1u8, 2u8, 3u8] @A"#
|
||||
),
|
||||
"[@A 1, @A 2, @A 3] : List (A U8)",
|
||||
);
|
||||
|
@ -1230,8 +1207,7 @@ fn dict_get_single() {
|
|||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
Dict.single 0 {a: 1, c: 2} |> Dict.get 0
|
||||
"#
|
||||
Dict.single 0 {a: 1, c: 2} |> Dict.get 0"#
|
||||
),
|
||||
r#"Ok { a: 1, c: 2 } : Result { a : Num *, c : Num * } [KeyNotFound]"#,
|
||||
)
|
||||
|
@ -1242,8 +1218,7 @@ fn record_of_poly_function() {
|
|||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
{ a: \_ -> "a" }
|
||||
"#
|
||||
{ a: \_ -> "a" }"#
|
||||
),
|
||||
r#"{ a: <function> } : { a : * -> Str }"#,
|
||||
);
|
||||
|
@ -1254,8 +1229,7 @@ fn record_of_poly_function_and_string() {
|
|||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
{ a: \_ -> "a", b: "b" }
|
||||
"#
|
||||
{ a: \_ -> "a", b: "b" }"#
|
||||
),
|
||||
r#"{ a: <function>, b: "b" } : { a : * -> Str, b : Str }"#,
|
||||
);
|
||||
|
@ -1266,8 +1240,7 @@ fn newtype_by_void_is_wrapped() {
|
|||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
Result.try (Err 42) (\x -> Err (x+1))
|
||||
"#
|
||||
Result.try (Err 42) (\x -> Err (x+1))"#
|
||||
),
|
||||
r#"Err 42 : Result b (Num *)"#,
|
||||
);
|
||||
|
@ -1275,8 +1248,7 @@ fn newtype_by_void_is_wrapped() {
|
|||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
Result.try (Ok 42) (\x -> Ok (x+1))
|
||||
"#
|
||||
Result.try (Ok 42) (\x -> Ok (x+1))"#
|
||||
),
|
||||
r#"Ok 43 : Result (Num *) err"#,
|
||||
);
|
||||
|
|
|
@ -7,7 +7,7 @@ use roc_load::MonomorphizedModule;
|
|||
use roc_parse::ast::Expr;
|
||||
use roc_repl_eval::{
|
||||
eval::jit_to_ast,
|
||||
gen::{compile_to_mono, format_answer, ReplOutput},
|
||||
gen::{compile_to_mono, format_answer},
|
||||
ReplApp, ReplAppMemory,
|
||||
};
|
||||
use roc_reporting::report::DEFAULT_PALETTE_HTML;
|
||||
|
@ -175,9 +175,31 @@ pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
|
|||
|
||||
// Compile the app
|
||||
let target_info = TargetInfo::default_wasm32();
|
||||
let mono = match compile_to_mono(arena, &src, target_info, DEFAULT_PALETTE_HTML) {
|
||||
Ok(m) => m,
|
||||
Err(messages) => return Err(messages.join("\n\n")),
|
||||
// TODO use this to filter out problems and warnings in wrapped defs.
|
||||
// See the variable by the same name in the CLI REPL for how to do this!
|
||||
let mono = match compile_to_mono(
|
||||
arena,
|
||||
std::iter::empty(),
|
||||
&src,
|
||||
target_info,
|
||||
DEFAULT_PALETTE_HTML,
|
||||
) {
|
||||
(Some(m), problems) if problems.is_empty() => m, // TODO render problems and continue if possible
|
||||
(_, problems) => {
|
||||
// TODO always report these, but continue if possible with the MonomorphizedModule if we have one.
|
||||
let mut buf = String::new();
|
||||
|
||||
// Join all the errors and warnings together with blank lines.
|
||||
for message in problems.errors.iter().chain(problems.warnings.iter()) {
|
||||
if !buf.is_empty() {
|
||||
buf.push_str("\n\n");
|
||||
}
|
||||
|
||||
buf.push_str(message);
|
||||
}
|
||||
|
||||
return Err(buf);
|
||||
}
|
||||
};
|
||||
|
||||
let MonomorphizedModule {
|
||||
|
@ -270,10 +292,11 @@ pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
|
|||
target_info,
|
||||
);
|
||||
|
||||
let var_name = String::new(); // TODO turn this into something like " # val1"
|
||||
|
||||
// Transform the Expr to a string
|
||||
// `Result::Err` becomes a JS exception that will be caught and displayed
|
||||
match format_answer(arena, res_answer, expr_type_str) {
|
||||
ReplOutput::NoProblems { expr, expr_type } => Ok(format!("{} : {}", expr, expr_type)),
|
||||
ReplOutput::Problems(lines) => Err(format!("\n{}\n", lines.join("\n\n"))),
|
||||
}
|
||||
let expr = format_answer(arena, res_answer);
|
||||
|
||||
Ok(format!("{expr} : {expr_type_str}{var_name}"))
|
||||
}
|
||||
|
|
|
@ -73,6 +73,41 @@ pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_getppid() -> libc::pid_t {
|
||||
libc::getppid()
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_mmap(
|
||||
addr: *mut libc::c_void,
|
||||
len: libc::size_t,
|
||||
prot: libc::c_int,
|
||||
flags: libc::c_int,
|
||||
fd: libc::c_int,
|
||||
offset: libc::off_t,
|
||||
) -> *mut libc::c_void {
|
||||
libc::mmap(addr, len, prot, flags, fd, offset)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_shm_open(
|
||||
name: *const libc::c_char,
|
||||
oflag: libc::c_int,
|
||||
mode: libc::mode_t,
|
||||
) -> libc::c_int {
|
||||
libc::shm_open(name, oflag, mode as libc::c_uint)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_send_signal(pid: libc::pid_t, sig: libc::c_int) -> libc::c_int {
|
||||
libc::kill(pid, sig)
|
||||
}
|
||||
|
||||
fn print_backtrace() {
|
||||
eprintln!("Here is the call stack that led to the crash:\n");
|
||||
|
||||
|
@ -263,7 +298,7 @@ pub extern "C" fn roc_fx_exePath(roc_str: &RocStr) -> RocResult<RocList<u8>, ()>
|
|||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn roc_fx_stdinLine() -> RocStr {
|
||||
use std::io::{self, BufRead};
|
||||
use std::io::BufRead;
|
||||
|
||||
let stdin = io::stdin();
|
||||
let line1 = stdin.lock().lines().next().unwrap().unwrap();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const std = @import("std");
|
||||
const str = @import("str");
|
||||
const builtin = @import("builtin");
|
||||
const str = @import("str");
|
||||
const RocStr = str.RocStr;
|
||||
const testing = std.testing;
|
||||
const expectEqual = testing.expectEqual;
|
||||
|
@ -85,6 +85,34 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
|
|||
return memset(dst, value, size);
|
||||
}
|
||||
|
||||
extern fn kill(pid: c_int, sig: c_int) c_int;
|
||||
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
|
||||
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
|
||||
extern fn getppid() c_int;
|
||||
|
||||
fn roc_getppid() callconv(.C) c_int {
|
||||
return getppid();
|
||||
}
|
||||
|
||||
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
|
||||
return kill(pid, sig);
|
||||
}
|
||||
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
|
||||
return shm_open(name, oflag, mode);
|
||||
}
|
||||
fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
|
||||
return mmap(addr, length, prot, flags, fd, offset);
|
||||
}
|
||||
|
||||
comptime {
|
||||
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
|
||||
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
|
||||
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
|
||||
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
|
||||
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
|
||||
}
|
||||
}
|
||||
|
||||
const Unit = extern struct {};
|
||||
|
||||
pub export fn main() u8 {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use core::alloc::Layout;
|
||||
use core::ffi::c_void;
|
||||
use core::mem::{ManuallyDrop, MaybeUninit};
|
||||
use core::mem::MaybeUninit;
|
||||
use libc;
|
||||
use roc_std::{RocList, RocStr};
|
||||
use std::env;
|
||||
|
@ -72,6 +72,41 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut
|
|||
libc::memset(dst, c, n)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_getppid() -> libc::pid_t {
|
||||
libc::getppid()
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_mmap(
|
||||
addr: *mut libc::c_void,
|
||||
len: libc::size_t,
|
||||
prot: libc::c_int,
|
||||
flags: libc::c_int,
|
||||
fd: libc::c_int,
|
||||
offset: libc::off_t,
|
||||
) -> *mut libc::c_void {
|
||||
libc::mmap(addr, len, prot, flags, fd, offset)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_shm_open(
|
||||
name: *const libc::c_char,
|
||||
oflag: libc::c_int,
|
||||
mode: libc::mode_t,
|
||||
) -> libc::c_int {
|
||||
libc::shm_open(name, oflag, mode as libc::c_uint)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_send_signal(pid: libc::pid_t, sig: libc::c_int) -> libc::c_int {
|
||||
libc::kill(pid, sig)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_main() -> i32 {
|
||||
let arg = env::args()
|
||||
|
@ -122,9 +157,7 @@ unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 {
|
|||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn roc_fx_getLine() -> RocStr {
|
||||
use std::io::{self, BufRead};
|
||||
|
||||
let stdin = io::stdin();
|
||||
let stdin = std::io::stdin();
|
||||
let line1 = stdin.lock().lines().next().unwrap().unwrap();
|
||||
|
||||
RocStr::from(line1.as_str())
|
||||
|
@ -132,11 +165,10 @@ pub extern "C" fn roc_fx_getLine() -> RocStr {
|
|||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn roc_fx_getChar() -> u8 {
|
||||
use std::io::{self, BufRead};
|
||||
let mut buffer = [0];
|
||||
|
||||
if let Err(ioerr) = io::stdin().lock().read_exact(&mut buffer[..]) {
|
||||
if ioerr.kind() == io::ErrorKind::UnexpectedEof {
|
||||
if let Err(ioerr) = std::io::stdin().lock().read_exact(&mut buffer[..]) {
|
||||
if ioerr.kind() == std::io::ErrorKind::UnexpectedEof {
|
||||
u8::MAX
|
||||
} else {
|
||||
panic!("Got an unexpected error while reading char from stdin");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const std = @import("std");
|
||||
const str = @import("str");
|
||||
const builtin = @import("builtin");
|
||||
const str = @import("str");
|
||||
const RocStr = str.RocStr;
|
||||
const testing = std.testing;
|
||||
const expectEqual = testing.expectEqual;
|
||||
|
@ -144,6 +144,34 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
|
|||
return memset(dst, value, size);
|
||||
}
|
||||
|
||||
extern fn kill(pid: c_int, sig: c_int) c_int;
|
||||
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
|
||||
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
|
||||
extern fn getppid() c_int;
|
||||
|
||||
fn roc_getppid() callconv(.C) c_int {
|
||||
return getppid();
|
||||
}
|
||||
|
||||
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
|
||||
return kill(pid, sig);
|
||||
}
|
||||
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
|
||||
return shm_open(name, oflag, mode);
|
||||
}
|
||||
fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
|
||||
return mmap(addr, length, prot, flags, fd, offset);
|
||||
}
|
||||
|
||||
comptime {
|
||||
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
|
||||
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
|
||||
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
|
||||
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
|
||||
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
|
||||
}
|
||||
}
|
||||
|
||||
const Unit = extern struct {};
|
||||
|
||||
pub export fn main() callconv(.C) u8 {
|
||||
|
|
|
@ -140,14 +140,14 @@ string : Str -> Parser RawStr Str
|
|||
string = \expectedString ->
|
||||
strToRaw expectedString
|
||||
|> stringRaw
|
||||
|> map (\_val -> expectedString)
|
||||
|> map \_val -> expectedString
|
||||
|
||||
scalar : U32 -> Parser RawStr U32
|
||||
scalar = \expectedScalar ->
|
||||
expectedScalar
|
||||
|> strFromScalar
|
||||
|> string
|
||||
|> map (\_ -> expectedScalar)
|
||||
|> map \_ -> expectedScalar
|
||||
|
||||
# Matches any codeunit
|
||||
anyCodeunit : Parser RawStr U8
|
||||
|
@ -181,7 +181,7 @@ digit =
|
|||
|> List.map \digitNum ->
|
||||
digitNum
|
||||
|> codeunit
|
||||
|> map (\_ -> digitNum)
|
||||
|> map \_ -> digitNum
|
||||
|
||||
oneOf digitParsers
|
||||
|
||||
|
@ -192,7 +192,7 @@ digits =
|
|||
|> map \digitsList ->
|
||||
digitsList
|
||||
|> List.map Num.intCast
|
||||
|> List.walk 0 (\sum, digitVal -> 10 * sum + digitVal)
|
||||
|> List.walk 0 \sum, digitVal -> 10 * sum + digitVal
|
||||
|
||||
## Try a bunch of different parsers.
|
||||
##
|
||||
|
|
|
@ -95,6 +95,11 @@ void *roc_memset(void *str, int c, size_t n) {
|
|||
return memset(str, c, n);
|
||||
}
|
||||
|
||||
int roc_send_signal(int pid, int sig) { return kill(pid, sig); }
|
||||
int roc_shm_open(char* name, int oflag, int mode) { return shm_open(name, oflag, mode); }
|
||||
void* roc_mmap(void* addr, int length, int prot, int flags, int fd, int offset) { return mmap(addr, length, prot, flags, fd, offset); }
|
||||
int roc_getppid() { return getppid(); }
|
||||
|
||||
struct RocStr {
|
||||
char *bytes;
|
||||
size_t len;
|
||||
|
|
|
@ -27,6 +27,12 @@ void* roc_memcpy(void* dest, const void* src, size_t n) {
|
|||
|
||||
void* roc_memset(void* str, int c, size_t n) { return memset(str, c, n); }
|
||||
|
||||
|
||||
int roc_send_signal(int pid, int sig) { return kill(pid, sig); }
|
||||
int roc_shm_open(char* name, int oflag, int mode) { return shm_open(name, oflag, mode); }
|
||||
void* roc_mmap(void* addr, int length, int prot, int flags, int fd, int offset) { return mmap(addr, length, prot, flags, fd, offset); }
|
||||
int roc_getppid() { return getppid(); }
|
||||
|
||||
struct RocStr {
|
||||
char* bytes;
|
||||
size_t len;
|
||||
|
|
|
@ -54,6 +54,41 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut
|
|||
libc::memset(dst, c, n)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_getppid() -> libc::pid_t {
|
||||
libc::getppid()
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_mmap(
|
||||
addr: *mut libc::c_void,
|
||||
len: libc::size_t,
|
||||
prot: libc::c_int,
|
||||
flags: libc::c_int,
|
||||
fd: libc::c_int,
|
||||
offset: libc::off_t,
|
||||
) -> *mut libc::c_void {
|
||||
libc::mmap(addr, len, prot, flags, fd, offset)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_shm_open(
|
||||
name: *const libc::c_char,
|
||||
oflag: libc::c_int,
|
||||
mode: libc::mode_t,
|
||||
) -> libc::c_int {
|
||||
libc::shm_open(name, oflag, mode as libc::c_uint)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_send_signal(pid: libc::pid_t, sig: libc::c_int) -> libc::c_int {
|
||||
libc::kill(pid, sig)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_main() -> i32 {
|
||||
let mut roc_str = RocStr::default();
|
||||
|
|
|
@ -75,6 +75,34 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
|
|||
return memset(dst, value, size);
|
||||
}
|
||||
|
||||
extern fn kill(pid: c_int, sig: c_int) c_int;
|
||||
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
|
||||
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
|
||||
extern fn getppid() c_int;
|
||||
|
||||
fn roc_getppid() callconv(.C) c_int {
|
||||
return getppid();
|
||||
}
|
||||
|
||||
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
|
||||
return kill(pid, sig);
|
||||
}
|
||||
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
|
||||
return shm_open(name, oflag, mode);
|
||||
}
|
||||
fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
|
||||
return mmap(addr, length, prot, flags, fd, offset);
|
||||
}
|
||||
|
||||
comptime {
|
||||
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
|
||||
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
|
||||
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
|
||||
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
|
||||
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
|
||||
}
|
||||
}
|
||||
|
||||
const mem = std.mem;
|
||||
const Allocator = mem.Allocator;
|
||||
|
||||
|
|
|
@ -27,6 +27,11 @@ void* roc_memcpy(void* dest, const void* src, size_t n) {
|
|||
|
||||
void* roc_memset(void* str, int c, size_t n) { return memset(str, c, n); }
|
||||
|
||||
int roc_send_signal(int pid, int sig) { return kill(pid, sig); }
|
||||
int roc_shm_open(char* name, int oflag, int mode) { return shm_open(name, oflag, mode); }
|
||||
void* roc_mmap(void* addr, int length, int prot, int flags, int fd, int offset) { return mmap(addr, length, prot, flags, fd, offset); }
|
||||
int roc_getppid() { return getppid(); }
|
||||
|
||||
struct RocStr {
|
||||
char* bytes;
|
||||
size_t len;
|
||||
|
|
|
@ -33,6 +33,41 @@ pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
|
|||
libc::free(c_ptr)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_getppid() -> libc::pid_t {
|
||||
libc::getppid()
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_mmap(
|
||||
addr: *mut libc::c_void,
|
||||
len: libc::size_t,
|
||||
prot: libc::c_int,
|
||||
flags: libc::c_int,
|
||||
fd: libc::c_int,
|
||||
offset: libc::off_t,
|
||||
) -> *mut libc::c_void {
|
||||
libc::mmap(addr, len, prot, flags, fd, offset)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_shm_open(
|
||||
name: *const libc::c_char,
|
||||
oflag: libc::c_int,
|
||||
mode: libc::mode_t,
|
||||
) -> libc::c_int {
|
||||
libc::shm_open(name, oflag, mode as libc::c_uint)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_send_signal(pid: libc::pid_t, sig: libc::c_int) -> libc::c_int {
|
||||
libc::kill(pid, sig)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_main() -> i32 {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue