mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-13 23:36:29 +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:
|
with:
|
||||||
use-quiet-mode: 'yes'
|
use-quiet-mode: 'yes'
|
||||||
use-verbose-mode: 'yes'
|
use-verbose-mode: 'yes'
|
||||||
|
check-modified-files-only: 'yes'
|
||||||
base-branch: 'main'
|
base-branch: 'main'
|
||||||
check-modified-files-only: 'yes'
|
check-modified-files-only: 'yes'
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -3234,6 +3234,7 @@ version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indoc",
|
"indoc",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"roc_build",
|
||||||
"roc_cli",
|
"roc_cli",
|
||||||
"roc_repl_cli",
|
"roc_repl_cli",
|
||||||
"roc_test_utils",
|
"roc_test_utils",
|
||||||
|
@ -3412,6 +3413,7 @@ dependencies = [
|
||||||
"roc_fmt",
|
"roc_fmt",
|
||||||
"roc_gen_llvm",
|
"roc_gen_llvm",
|
||||||
"roc_glue",
|
"roc_glue",
|
||||||
|
"roc_intern",
|
||||||
"roc_linker",
|
"roc_linker",
|
||||||
"roc_load",
|
"roc_load",
|
||||||
"roc_module",
|
"roc_module",
|
||||||
|
@ -3921,6 +3923,7 @@ dependencies = [
|
||||||
"rustyline",
|
"rustyline",
|
||||||
"rustyline-derive",
|
"rustyline-derive",
|
||||||
"target-lexicon",
|
"target-lexicon",
|
||||||
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3958,6 +3961,7 @@ dependencies = [
|
||||||
"roc_builtins",
|
"roc_builtins",
|
||||||
"roc_can",
|
"roc_can",
|
||||||
"roc_collections",
|
"roc_collections",
|
||||||
|
"roc_error_macros",
|
||||||
"roc_gen_llvm",
|
"roc_gen_llvm",
|
||||||
"roc_intern",
|
"roc_intern",
|
||||||
"roc_load",
|
"roc_load",
|
||||||
|
@ -5165,9 +5169,9 @@ checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.9.0"
|
version = "1.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
|
checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
|
|
|
@ -60,6 +60,7 @@ roc_editor = { path = "../editor", optional = true }
|
||||||
roc_linker = { path = "../linker" }
|
roc_linker = { path = "../linker" }
|
||||||
roc_repl_cli = { path = "../repl_cli", optional = true }
|
roc_repl_cli = { path = "../repl_cli", optional = true }
|
||||||
roc_tracing = { path = "../tracing" }
|
roc_tracing = { path = "../tracing" }
|
||||||
|
roc_intern = { path = "../compiler/intern" }
|
||||||
clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions"] }
|
clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions"] }
|
||||||
const_format = { version = "0.2.23", features = ["const_generics"] }
|
const_format = { version = "0.2.23", features = ["const_generics"] }
|
||||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use roc_build::{
|
use roc_build::{
|
||||||
link::{link, preprocess_host_wasm32, rebuild_host, LinkType, LinkingStrategy},
|
link::{link, preprocess_host_wasm32, rebuild_host, LinkType, LinkingStrategy},
|
||||||
program::{self, Problems},
|
program::{self, CodeGenOptions, Problems},
|
||||||
};
|
};
|
||||||
use roc_builtins::bitcode;
|
use roc_builtins::bitcode;
|
||||||
use roc_collections::VecMap;
|
|
||||||
use roc_load::{
|
use roc_load::{
|
||||||
EntryPoint, ExecutionMode, Expectations, LoadConfig, LoadMonomorphizedError, LoadedModule,
|
EntryPoint, ExecutionMode, ExpectMetadata, LoadConfig, LoadMonomorphizedError, LoadedModule,
|
||||||
LoadingProblem, Threading,
|
LoadingProblem, Threading,
|
||||||
};
|
};
|
||||||
use roc_module::symbol::{Interns, ModuleId};
|
|
||||||
use roc_mono::ir::OptLevel;
|
use roc_mono::ir::OptLevel;
|
||||||
use roc_reporting::report::RenderTarget;
|
use roc_reporting::report::RenderTarget;
|
||||||
use roc_target::TargetInfo;
|
use roc_target::TargetInfo;
|
||||||
|
@ -30,12 +28,11 @@ fn report_timing(buf: &mut String, label: &str, duration: Duration) {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BuiltFile {
|
pub struct BuiltFile<'a> {
|
||||||
pub binary_path: PathBuf,
|
pub binary_path: PathBuf,
|
||||||
pub problems: Problems,
|
pub problems: Problems,
|
||||||
pub total_time: Duration,
|
pub total_time: Duration,
|
||||||
pub expectations: VecMap<ModuleId, Expectations>,
|
pub expect_metadata: ExpectMetadata<'a>,
|
||||||
pub interns: Interns,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum BuildOrdering {
|
pub enum BuildOrdering {
|
||||||
|
@ -60,8 +57,7 @@ pub fn build_file<'a>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
target: &Triple,
|
target: &Triple,
|
||||||
app_module_path: PathBuf,
|
app_module_path: PathBuf,
|
||||||
opt_level: OptLevel,
|
code_gen_options: CodeGenOptions,
|
||||||
emit_debug_info: bool,
|
|
||||||
emit_timings: bool,
|
emit_timings: bool,
|
||||||
link_type: LinkType,
|
link_type: LinkType,
|
||||||
linking_strategy: LinkingStrategy,
|
linking_strategy: LinkingStrategy,
|
||||||
|
@ -69,7 +65,7 @@ pub fn build_file<'a>(
|
||||||
threading: Threading,
|
threading: Threading,
|
||||||
wasm_dev_stack_bytes: Option<u32>,
|
wasm_dev_stack_bytes: Option<u32>,
|
||||||
order: BuildOrdering,
|
order: BuildOrdering,
|
||||||
) -> Result<BuiltFile, BuildFileError<'a>> {
|
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
|
||||||
let compilation_start = Instant::now();
|
let compilation_start = Instant::now();
|
||||||
let target_info = TargetInfo::from(target);
|
let target_info = TargetInfo::from(target);
|
||||||
|
|
||||||
|
@ -121,7 +117,7 @@ pub fn build_file<'a>(
|
||||||
|
|
||||||
match roc_target::OperatingSystem::from(target.operating_system) {
|
match roc_target::OperatingSystem::from(target.operating_system) {
|
||||||
Wasi => {
|
Wasi => {
|
||||||
if matches!(opt_level, OptLevel::Development) {
|
if matches!(code_gen_options.opt_level, OptLevel::Development) {
|
||||||
("wasm", "wasm", Some("wasm"))
|
("wasm", "wasm", Some("wasm"))
|
||||||
} else {
|
} else {
|
||||||
("zig", "bc", Some("wasm"))
|
("zig", "bc", Some("wasm"))
|
||||||
|
@ -180,7 +176,7 @@ pub fn build_file<'a>(
|
||||||
};
|
};
|
||||||
|
|
||||||
let rebuild_thread = spawn_rebuild_thread(
|
let rebuild_thread = spawn_rebuild_thread(
|
||||||
opt_level,
|
code_gen_options.opt_level,
|
||||||
linking_strategy,
|
linking_strategy,
|
||||||
prebuilt,
|
prebuilt,
|
||||||
host_input_path.clone(),
|
host_input_path.clone(),
|
||||||
|
@ -241,11 +237,8 @@ pub fn build_file<'a>(
|
||||||
// inside a nested scope without causing a borrow error!
|
// inside a nested scope without causing a borrow error!
|
||||||
let mut loaded = loaded;
|
let mut loaded = loaded;
|
||||||
let problems = program::report_problems_monomorphized(&mut loaded);
|
let problems = program::report_problems_monomorphized(&mut loaded);
|
||||||
let expectations = std::mem::take(&mut loaded.expectations);
|
|
||||||
let loaded = loaded;
|
let loaded = loaded;
|
||||||
|
|
||||||
let interns = loaded.interns.clone();
|
|
||||||
|
|
||||||
enum HostRebuildTiming {
|
enum HostRebuildTiming {
|
||||||
BeforeApp(u128),
|
BeforeApp(u128),
|
||||||
ConcurrentWithApp(JoinHandle<u128>),
|
ConcurrentWithApp(JoinHandle<u128>),
|
||||||
|
@ -266,13 +259,12 @@ pub fn build_file<'a>(
|
||||||
HostRebuildTiming::ConcurrentWithApp(rebuild_thread)
|
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,
|
arena,
|
||||||
loaded,
|
loaded,
|
||||||
&app_module_path,
|
&app_module_path,
|
||||||
target,
|
target,
|
||||||
opt_level,
|
code_gen_options,
|
||||||
emit_debug_info,
|
|
||||||
&preprocessed_host_path,
|
&preprocessed_host_path,
|
||||||
wasm_dev_stack_bytes,
|
wasm_dev_stack_bytes,
|
||||||
);
|
);
|
||||||
|
@ -351,7 +343,7 @@ pub fn build_file<'a>(
|
||||||
|
|
||||||
let str_host_obj_path = bitcode::get_builtins_host_obj_path();
|
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);
|
inputs.push(&str_host_obj_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -388,8 +380,7 @@ pub fn build_file<'a>(
|
||||||
binary_path,
|
binary_path,
|
||||||
problems,
|
problems,
|
||||||
total_time,
|
total_time,
|
||||||
interns,
|
expect_metadata,
|
||||||
expectations,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,9 @@ use build::BuiltFile;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use clap::{Arg, ArgMatches, Command, ValueSource};
|
use clap::{Arg, ArgMatches, Command, ValueSource};
|
||||||
use roc_build::link::{LinkType, LinkingStrategy};
|
use roc_build::link::{LinkType, LinkingStrategy};
|
||||||
use roc_build::program::Problems;
|
use roc_build::program::{CodeGenBackend, CodeGenOptions, Problems};
|
||||||
use roc_collections::VecMap;
|
|
||||||
use roc_error_macros::{internal_error, user_error};
|
use roc_error_macros::{internal_error, user_error};
|
||||||
use roc_load::{Expectations, LoadingProblem, Threading};
|
use roc_load::{ExpectMetadata, LoadingProblem, Threading};
|
||||||
use roc_module::symbol::{Interns, ModuleId};
|
|
||||||
use roc_mono::ir::OptLevel;
|
use roc_mono::ir::OptLevel;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::{CString, OsStr};
|
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 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,
|
&mut writer,
|
||||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||||
arena,
|
arena,
|
||||||
|
@ -481,18 +479,33 @@ pub fn build(
|
||||||
use build::build_file;
|
use build::build_file;
|
||||||
use BuildConfig::*;
|
use BuildConfig::*;
|
||||||
|
|
||||||
let arena = Bump::new();
|
// the process will end after this function,
|
||||||
let filename = matches.value_of_os(ROC_FILE).unwrap();
|
// so we don't want to spend time freeing these values
|
||||||
let opt_level = match (
|
let arena = ManuallyDrop::new(Bump::new());
|
||||||
matches.is_present(FLAG_OPTIMIZE),
|
|
||||||
matches.is_present(FLAG_OPT_SIZE),
|
let code_gen_backend = if matches!(triple.architecture, Architecture::Wasm32) {
|
||||||
matches.is_present(FLAG_DEV),
|
CodeGenBackend::Wasm
|
||||||
) {
|
} else {
|
||||||
(true, false, false) => OptLevel::Optimize,
|
match matches.is_present(FLAG_DEV) {
|
||||||
(false, true, false) => OptLevel::Size,
|
true => CodeGenBackend::Assembly,
|
||||||
(false, false, true) => OptLevel::Development,
|
false => CodeGenBackend::Llvm,
|
||||||
(false, false, false) => OptLevel::Normal,
|
}
|
||||||
_ => user_error!("build can be only one of `--dev`, `--optimize`, or `--opt-size`"),
|
};
|
||||||
|
|
||||||
|
let opt_level = if let BuildConfig::BuildAndRunIfNoErrors = config {
|
||||||
|
OptLevel::Development
|
||||||
|
} else {
|
||||||
|
match (
|
||||||
|
matches.is_present(FLAG_OPTIMIZE),
|
||||||
|
matches.is_present(FLAG_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_debug_info = matches.is_present(FLAG_DEBUG);
|
||||||
let emit_timings = matches.is_present(FLAG_TIME);
|
let emit_timings = matches.is_present(FLAG_TIME);
|
||||||
|
@ -508,7 +521,7 @@ pub fn build(
|
||||||
};
|
};
|
||||||
|
|
||||||
let wasm_dev_backend = matches!(opt_level, OptLevel::Development)
|
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 {
|
let linking_strategy = if wasm_dev_backend {
|
||||||
LinkingStrategy::Additive
|
LinkingStrategy::Additive
|
||||||
|
@ -528,6 +541,8 @@ pub fn build(
|
||||||
// We make an exception for Wasm, because cross-compiling is the norm in that case.
|
// We make an exception for Wasm, because cross-compiling is the norm in that case.
|
||||||
triple != Triple::host() && !matches!(triple.architecture, Architecture::Wasm32)
|
triple != Triple::host() && !matches!(triple.architecture, Architecture::Wasm32)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let filename = matches.value_of_os(ROC_FILE).unwrap();
|
||||||
let path = Path::new(filename);
|
let path = Path::new(filename);
|
||||||
|
|
||||||
// Spawn the root task
|
// Spawn the root task
|
||||||
|
@ -559,12 +574,18 @@ pub fn build(
|
||||||
BuildAndRunIfNoErrors => BuildOrdering::BuildIfChecks,
|
BuildAndRunIfNoErrors => BuildOrdering::BuildIfChecks,
|
||||||
_ => BuildOrdering::AlwaysBuild,
|
_ => BuildOrdering::AlwaysBuild,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let code_gen_options = CodeGenOptions {
|
||||||
|
backend: code_gen_backend,
|
||||||
|
opt_level,
|
||||||
|
emit_debug_info,
|
||||||
|
};
|
||||||
|
|
||||||
let res_binary_path = build_file(
|
let res_binary_path = build_file(
|
||||||
&arena,
|
&arena,
|
||||||
&triple,
|
&triple,
|
||||||
path.to_path_buf(),
|
path.to_path_buf(),
|
||||||
opt_level,
|
code_gen_options,
|
||||||
emit_debug_info,
|
|
||||||
emit_timings,
|
emit_timings,
|
||||||
link_type,
|
link_type,
|
||||||
linking_strategy,
|
linking_strategy,
|
||||||
|
@ -579,8 +600,7 @@ pub fn build(
|
||||||
binary_path,
|
binary_path,
|
||||||
problems,
|
problems,
|
||||||
total_time,
|
total_time,
|
||||||
expectations,
|
expect_metadata,
|
||||||
interns,
|
|
||||||
}) => {
|
}) => {
|
||||||
match config {
|
match config {
|
||||||
BuildOnly => {
|
BuildOnly => {
|
||||||
|
@ -593,7 +613,7 @@ pub fn build(
|
||||||
|
|
||||||
// No need to waste time freeing this memory,
|
// No need to waste time freeing this memory,
|
||||||
// since the process is about to exit anyway.
|
// since the process is about to exit anyway.
|
||||||
std::mem::forget(arena);
|
// std::mem::forget(arena);
|
||||||
|
|
||||||
print_problems(problems, total_time);
|
print_problems(problems, total_time);
|
||||||
println!(" while successfully building:\n\n {generated_filename}");
|
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
|
// ManuallyDrop will leak the bytes because we don't drop manually
|
||||||
let bytes = &ManuallyDrop::new(std::fs::read(&binary_path).unwrap());
|
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 => {
|
BuildAndRunIfNoErrors => {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
|
@ -637,7 +657,7 @@ pub fn build(
|
||||||
// ManuallyDrop will leak the bytes because we don't drop manually
|
// ManuallyDrop will leak the bytes because we don't drop manually
|
||||||
let bytes = &ManuallyDrop::new(std::fs::read(&binary_path).unwrap());
|
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>>(
|
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,
|
opt_level: OptLevel,
|
||||||
triple: Triple,
|
triple: Triple,
|
||||||
args: I,
|
args: I,
|
||||||
binary_bytes: &[u8],
|
binary_bytes: &[u8],
|
||||||
expectations: VecMap<ModuleId, Expectations>,
|
expect_metadata: ExpectMetadata,
|
||||||
interns: Interns,
|
|
||||||
) -> io::Result<i32> {
|
) -> io::Result<i32> {
|
||||||
match triple.architecture {
|
match triple.architecture {
|
||||||
Architecture::Wasm32 => {
|
Architecture::Wasm32 => {
|
||||||
|
@ -720,10 +739,6 @@ fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
|
||||||
.strip_prefix(env::current_dir().unwrap())
|
.strip_prefix(env::current_dir().unwrap())
|
||||||
.unwrap_or(path);
|
.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")]
|
#[cfg(target_family = "unix")]
|
||||||
{
|
{
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
@ -748,7 +763,7 @@ fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
|
||||||
|
|
||||||
Ok(0)
|
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)
|
/// Run on the native OS (not on wasm)
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
|
fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
|
||||||
arena: Bump,
|
arena: &Bump,
|
||||||
opt_level: OptLevel,
|
opt_level: OptLevel,
|
||||||
args: I,
|
args: I,
|
||||||
binary_bytes: &[u8],
|
binary_bytes: &[u8],
|
||||||
expectations: VecMap<ModuleId, Expectations>,
|
expect_metadata: ExpectMetadata,
|
||||||
interns: Interns,
|
|
||||||
) -> std::io::Result<i32> {
|
) -> std::io::Result<i32> {
|
||||||
use bumpalo::collections::CollectIn;
|
use bumpalo::collections::CollectIn;
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let executable = roc_run_executable_file_path(binary_bytes)?;
|
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
|
let argv: bumpalo::collections::Vec<*const c_char> = argv_cstrings
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| s.as_ptr())
|
.map(|s| s.as_ptr())
|
||||||
.chain([std::ptr::null()])
|
.chain([std::ptr::null()])
|
||||||
.collect_in(&arena);
|
.collect_in(arena);
|
||||||
|
|
||||||
let envp: bumpalo::collections::Vec<*const c_char> = envp_cstrings
|
let envp: bumpalo::collections::Vec<*const c_char> = envp_cstrings
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| s.as_ptr())
|
.map(|s| s.as_ptr())
|
||||||
.chain([std::ptr::null()])
|
.chain([std::ptr::null()])
|
||||||
.collect_in(&arena);
|
.collect_in(arena);
|
||||||
|
|
||||||
match opt_level {
|
match opt_level {
|
||||||
OptLevel::Development => {
|
OptLevel::Development => roc_dev_native(arena, executable, argv, envp, expect_metadata),
|
||||||
roc_run_native_debug(executable, &argv, &envp, expectations, interns)
|
|
||||||
}
|
|
||||||
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => {
|
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => {
|
||||||
roc_run_native_fast(executable, &argv, &envp);
|
roc_run_native_fast(executable, &argv, &envp);
|
||||||
}
|
}
|
||||||
|
@ -950,14 +962,76 @@ impl ExecutableFile {
|
||||||
|
|
||||||
// with Expect
|
// with Expect
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
unsafe fn roc_run_native_debug(
|
fn roc_dev_native(
|
||||||
_executable: ExecutableFile,
|
arena: &Bump,
|
||||||
_argv: &[*const c_char],
|
executable: ExecutableFile,
|
||||||
_envp: &[*const c_char],
|
argv: bumpalo::collections::Vec<*const c_char>,
|
||||||
_expectations: VecMap<ModuleId, Expectations>,
|
envp: bumpalo::collections::Vec<*const c_char>,
|
||||||
_interns: Interns,
|
expect_metadata: ExpectMetadata,
|
||||||
) {
|
) -> ! {
|
||||||
todo!()
|
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")]
|
#[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)
|
/// Run on the native OS (not on wasm)
|
||||||
#[cfg(not(target_family = "unix"))]
|
#[cfg(not(target_family = "unix"))]
|
||||||
fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
|
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,
|
opt_level: OptLevel,
|
||||||
args: I,
|
args: I,
|
||||||
binary_bytes: &[u8],
|
binary_bytes: &[u8],
|
||||||
_expectations: VecMap<ModuleId, Expectations>,
|
_expect_metadata: ExpectMetadata,
|
||||||
_interns: Interns,
|
|
||||||
) -> io::Result<i32> {
|
) -> io::Result<i32> {
|
||||||
use bumpalo::collections::CollectIn;
|
use bumpalo::collections::CollectIn;
|
||||||
|
|
||||||
|
@ -1055,18 +1128,18 @@ fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| s.as_ptr())
|
.map(|s| s.as_ptr())
|
||||||
.chain([std::ptr::null()])
|
.chain([std::ptr::null()])
|
||||||
.collect_in(&arena);
|
.collect_in(arena);
|
||||||
|
|
||||||
let envp: bumpalo::collections::Vec<*const c_char> = envp_cstrings
|
let envp: bumpalo::collections::Vec<*const c_char> = envp_cstrings
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| s.as_ptr())
|
.map(|s| s.as_ptr())
|
||||||
.chain([std::ptr::null()])
|
.chain([std::ptr::null()])
|
||||||
.collect_in(&arena);
|
.collect_in(arena);
|
||||||
|
|
||||||
match opt_level {
|
match opt_level {
|
||||||
OptLevel::Development => {
|
OptLevel::Development => {
|
||||||
// roc_run_native_debug(executable, &argv, &envp, expectations, interns)
|
// 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 => {
|
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => {
|
||||||
roc_run_native_fast(executable, &argv, &envp);
|
roc_run_native_fast(executable, &argv, &envp);
|
||||||
|
|
|
@ -178,14 +178,7 @@ fn main() -> io::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some((CMD_REPL, _)) => {
|
Some((CMD_REPL, _)) => Ok(roc_repl_cli::main()),
|
||||||
{
|
|
||||||
roc_repl_cli::main()?;
|
|
||||||
|
|
||||||
// Exit 0 if the repl exited normally
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some((CMD_EDIT, matches)) => {
|
Some((CMD_EDIT, matches)) => {
|
||||||
match matches
|
match matches
|
||||||
.values_of_os(DIRECTORY_OR_FILES)
|
.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);
|
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 {};
|
const Unit = extern struct {};
|
||||||
|
|
||||||
pub export fn main() i32 {
|
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);
|
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 {};
|
const Unit = extern struct {};
|
||||||
|
|
||||||
pub export fn main() i32 {
|
pub export fn main() i32 {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const expectEqual = testing.expectEqual;
|
const expectEqual = testing.expectEqual;
|
||||||
const expect = testing.expect;
|
const expect = testing.expect;
|
||||||
|
@ -13,7 +14,6 @@ comptime {
|
||||||
// -fcompiler-rt in link.rs instead of doing this. Note that this
|
// -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
|
// workaround is present in many host.zig files, so make sure to undo
|
||||||
// it everywhere!
|
// it everywhere!
|
||||||
const builtin = @import("builtin");
|
|
||||||
if (builtin.os.tag == .macos) {
|
if (builtin.os.tag == .macos) {
|
||||||
_ = @import("compiler_rt");
|
_ = @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);
|
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 {
|
pub export fn main() u8 {
|
||||||
const stdout = std.io.getStdOut().writer();
|
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);
|
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
|
// warning! the array is currently stack-allocated so don't make this too big
|
||||||
const NUM_NUMS = 100;
|
const NUM_NUMS = 100;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const str = @import("str");
|
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
const str = @import("str");
|
||||||
const RocStr = str.RocStr;
|
const RocStr = str.RocStr;
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const expectEqual = testing.expectEqual;
|
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);
|
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 {};
|
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
|
// 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 size = std.math.max(@intCast(usize, roc__mainForHost_size()), 8);
|
||||||
const raw_output = roc_alloc(@intCast(usize, size), @alignOf(u64)).?;
|
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 nanos = timer.read();
|
||||||
const seconds = (@intToFloat(f64, nanos) / 1_000_000_000.0);
|
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;
|
stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -3,7 +3,7 @@ use roc_error_macros::internal_error;
|
||||||
pub use roc_gen_llvm::llvm::build::FunctionIterator;
|
pub use roc_gen_llvm::llvm::build::FunctionIterator;
|
||||||
use roc_gen_llvm::llvm::build::{module_from_builtins, LlvmBackendMode};
|
use roc_gen_llvm::llvm::build::{module_from_builtins, LlvmBackendMode};
|
||||||
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
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_module::symbol::{Interns, ModuleId};
|
||||||
use roc_mono::ir::OptLevel;
|
use roc_mono::ir::OptLevel;
|
||||||
use roc_region::all::LineInfo;
|
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)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn gen_from_mono_module(
|
pub fn gen_from_mono_module<'a>(
|
||||||
arena: &bumpalo::Bump,
|
arena: &'a bumpalo::Bump,
|
||||||
loaded: MonomorphizedModule,
|
loaded: MonomorphizedModule<'a>,
|
||||||
roc_file_path: &Path,
|
roc_file_path: &Path,
|
||||||
target: &target_lexicon::Triple,
|
target: &target_lexicon::Triple,
|
||||||
opt_level: OptLevel,
|
code_gen_options: CodeGenOptions,
|
||||||
emit_debug_info: bool,
|
|
||||||
preprocessed_host_path: &Path,
|
preprocessed_host_path: &Path,
|
||||||
wasm_dev_stack_bytes: Option<u32>,
|
wasm_dev_stack_bytes: Option<u32>,
|
||||||
) -> (CodeObject, CodeGenTiming) {
|
) -> GenFromMono<'a> {
|
||||||
match opt_level {
|
match code_gen_options.backend {
|
||||||
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => gen_from_mono_module_llvm(
|
CodeGenBackend::Assembly => gen_from_mono_module_dev(
|
||||||
arena,
|
|
||||||
loaded,
|
|
||||||
roc_file_path,
|
|
||||||
target,
|
|
||||||
opt_level,
|
|
||||||
emit_debug_info,
|
|
||||||
),
|
|
||||||
OptLevel::Development => gen_from_mono_module_dev(
|
|
||||||
arena,
|
arena,
|
||||||
loaded,
|
loaded,
|
||||||
target,
|
target,
|
||||||
preprocessed_host_path,
|
preprocessed_host_path,
|
||||||
wasm_dev_stack_bytes,
|
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 how should imported modules factor into this? What if those use builtins too?
|
||||||
// TODO this should probably use more helper functions
|
// TODO this should probably use more helper functions
|
||||||
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
|
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
|
||||||
fn gen_from_mono_module_llvm(
|
fn gen_from_mono_module_llvm<'a>(
|
||||||
arena: &bumpalo::Bump,
|
arena: &'a bumpalo::Bump,
|
||||||
loaded: MonomorphizedModule,
|
loaded: MonomorphizedModule<'a>,
|
||||||
roc_file_path: &Path,
|
roc_file_path: &Path,
|
||||||
target: &target_lexicon::Triple,
|
target: &target_lexicon::Triple,
|
||||||
opt_level: OptLevel,
|
code_gen_options: CodeGenOptions,
|
||||||
emit_debug_info: bool,
|
) -> GenFromMono<'a> {
|
||||||
) -> (CodeObject, CodeGenTiming) {
|
|
||||||
use crate::target::{self, convert_opt_level};
|
use crate::target::{self, convert_opt_level};
|
||||||
use inkwell::attributes::{Attribute, AttributeLoc};
|
use inkwell::attributes::{Attribute, AttributeLoc};
|
||||||
use inkwell::context::Context;
|
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 builder = context.create_builder();
|
||||||
let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module);
|
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);
|
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,
|
interns: loaded.interns,
|
||||||
module,
|
module,
|
||||||
target_info,
|
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(),
|
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),
|
CodeObject::MemoryBuffer(memory_buffer),
|
||||||
CodeGenTiming { code_gen },
|
CodeGenTiming { code_gen },
|
||||||
|
ExpectMetadata {
|
||||||
|
interns: env.interns,
|
||||||
|
layout_interner: loaded.layout_interner,
|
||||||
|
expectations: loaded.expectations,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "target-wasm32")]
|
#[cfg(feature = "target-wasm32")]
|
||||||
fn gen_from_mono_module_dev(
|
fn gen_from_mono_module_dev<'a>(
|
||||||
arena: &bumpalo::Bump,
|
arena: &'a bumpalo::Bump,
|
||||||
loaded: MonomorphizedModule,
|
loaded: MonomorphizedModule<'a>,
|
||||||
target: &target_lexicon::Triple,
|
target: &target_lexicon::Triple,
|
||||||
preprocessed_host_path: &Path,
|
preprocessed_host_path: &Path,
|
||||||
wasm_dev_stack_bytes: Option<u32>,
|
wasm_dev_stack_bytes: Option<u32>,
|
||||||
) -> (CodeObject, CodeGenTiming) {
|
) -> GenFromMono<'a> {
|
||||||
use target_lexicon::Architecture;
|
use target_lexicon::Architecture;
|
||||||
|
|
||||||
match target.architecture {
|
match target.architecture {
|
||||||
|
@ -458,13 +486,13 @@ fn gen_from_mono_module_dev(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "target-wasm32"))]
|
#[cfg(not(feature = "target-wasm32"))]
|
||||||
pub fn gen_from_mono_module_dev(
|
pub fn gen_from_mono_module_dev<'a>(
|
||||||
arena: &bumpalo::Bump,
|
arena: &'a bumpalo::Bump,
|
||||||
loaded: MonomorphizedModule,
|
loaded: MonomorphizedModule<'a>,
|
||||||
target: &target_lexicon::Triple,
|
target: &target_lexicon::Triple,
|
||||||
_host_input_path: &Path,
|
_host_input_path: &Path,
|
||||||
_wasm_dev_stack_bytes: Option<u32>,
|
_wasm_dev_stack_bytes: Option<u32>,
|
||||||
) -> (CodeObject, CodeGenTiming) {
|
) -> GenFromMono<'a> {
|
||||||
use target_lexicon::Architecture;
|
use target_lexicon::Architecture;
|
||||||
|
|
||||||
match target.architecture {
|
match target.architecture {
|
||||||
|
@ -476,12 +504,12 @@ pub fn gen_from_mono_module_dev(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "target-wasm32")]
|
#[cfg(feature = "target-wasm32")]
|
||||||
fn gen_from_mono_module_dev_wasm32(
|
fn gen_from_mono_module_dev_wasm32<'a>(
|
||||||
arena: &bumpalo::Bump,
|
arena: &'a bumpalo::Bump,
|
||||||
loaded: MonomorphizedModule,
|
loaded: MonomorphizedModule<'a>,
|
||||||
preprocessed_host_path: &Path,
|
preprocessed_host_path: &Path,
|
||||||
wasm_dev_stack_bytes: Option<u32>,
|
wasm_dev_stack_bytes: Option<u32>,
|
||||||
) -> (CodeObject, CodeGenTiming) {
|
) -> GenFromMono<'a> {
|
||||||
let code_gen_start = Instant::now();
|
let code_gen_start = Instant::now();
|
||||||
let MonomorphizedModule {
|
let MonomorphizedModule {
|
||||||
module_id,
|
module_id,
|
||||||
|
@ -530,14 +558,19 @@ fn gen_from_mono_module_dev_wasm32(
|
||||||
(
|
(
|
||||||
CodeObject::Vector(final_binary_bytes),
|
CodeObject::Vector(final_binary_bytes),
|
||||||
CodeGenTiming { code_gen },
|
CodeGenTiming { code_gen },
|
||||||
|
ExpectMetadata {
|
||||||
|
interns,
|
||||||
|
layout_interner,
|
||||||
|
expectations: loaded.expectations,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen_from_mono_module_dev_assembly(
|
fn gen_from_mono_module_dev_assembly<'a>(
|
||||||
arena: &bumpalo::Bump,
|
arena: &'a bumpalo::Bump,
|
||||||
loaded: MonomorphizedModule,
|
loaded: MonomorphizedModule<'a>,
|
||||||
target: &target_lexicon::Triple,
|
target: &target_lexicon::Triple,
|
||||||
) -> (CodeObject, CodeGenTiming) {
|
) -> GenFromMono<'a> {
|
||||||
let code_gen_start = Instant::now();
|
let code_gen_start = Instant::now();
|
||||||
|
|
||||||
let lazy_literals = true;
|
let lazy_literals = true;
|
||||||
|
@ -569,5 +602,13 @@ fn gen_from_mono_module_dev_assembly(
|
||||||
.write()
|
.write()
|
||||||
.expect("failed to build output object");
|
.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 std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
const SIGUSR1: c_int = 10;
|
const SIGUSR1: c_int = 10;
|
||||||
|
|
||||||
|
@ -18,6 +19,71 @@ pub fn setSharedBuffer(ptr: [*]u8, length: usize) callconv(.C) usize {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expectFailedStart() callconv(.C) [*]u8 {
|
pub fn expectFailedStartSharedBuffer() callconv(.C) [*]u8 {
|
||||||
return SHARED_BUFFER.ptr;
|
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 });
|
@export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });
|
||||||
|
|
||||||
if (builtin.target.cpu.arch != .wasm32) {
|
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
|
// sets the buffer used for expect failures
|
||||||
@export(expect.setSharedBuffer, .{ .name = "set_shared_buffer", .linkage = .Weak });
|
@export(expect.setSharedBuffer, .{ .name = "set_shared_buffer", .linkage = .Weak });
|
||||||
|
|
||||||
|
exportUtilsFn(expect.readSharedBufferEnv, "read_env_shared_buffer");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (builtin.target.cpu.arch == .aarch64) {
|
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)
|
// 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 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 {
|
comptime {
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
// During tests, use the testing allocators to satisfy these functions.
|
// 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_dealloc, .{ .name = "roc_dealloc", .linkage = .Strong });
|
||||||
@export(testing_roc_panic, .{ .name = "roc_panic", .linkage = .Strong });
|
@export(testing_roc_panic, .{ .name = "roc_panic", .linkage = .Strong });
|
||||||
@export(testing_roc_memcpy, .{ .name = "roc_memcpy", .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.
|
// Flaky test errors that only occur sometimes on MacOS ci server.
|
||||||
if error_str.contains("FileNotFound")
|
if error_str.contains("FileNotFound")
|
||||||
|| error_str.contains("unable to save cached ZIR code")
|
|| error_str.contains("unable to save cached ZIR code")
|
||||||
|
|| error_str.contains("LLVM failed to emit asm")
|
||||||
{
|
{
|
||||||
if flaky_fail_counter == 10 {
|
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);
|
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:
|
## You can use it in a pipeline:
|
||||||
##
|
##
|
||||||
## [2, 4, 8]
|
## [2, 4, 8]
|
||||||
## |> List.walk { start: 0, step: Num.add }
|
## |> List.walk 0 Num.add
|
||||||
##
|
##
|
||||||
## This returns 14 because:
|
## 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`.
|
## * 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
|
## 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 | |
|
||||||
## 0 | 2 | 2
|
## 0 | 2 | 2
|
||||||
## 2 | 4 | 6
|
## 2 | 4 | 6
|
||||||
## 6 | 8 | 14
|
## 6 | 8 | 14
|
||||||
##
|
##
|
||||||
## So `state` goes through these changes:
|
## The following returns -6:
|
||||||
## 1. `0` (because of `start: 0`)
|
|
||||||
## 2. `1` (because of `Num.add state elem` with `state` = 0 and `elem` = 1
|
|
||||||
##
|
##
|
||||||
## [1, 2, 3]
|
## [1, 2, 3]
|
||||||
## |> List.walk { start: 0, step: Num.sub }
|
## |> List.walk 0 Num.sub
|
||||||
##
|
|
||||||
## This returns -6 because
|
|
||||||
##
|
##
|
||||||
## Note that in other languages, `walk` is sometimes called `reduce`,
|
## Note that in other languages, `walk` is sometimes called `reduce`,
|
||||||
## `fold`, `foldLeft`, or `foldl`.
|
## `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: &str = "roc_builtins.utils.decref";
|
||||||
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";
|
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_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_LONGJMP: &str = "longjmp";
|
||||||
pub const UTILS_SETJMP: &str = "setjmp";
|
pub const UTILS_SETJMP: &str = "setjmp";
|
||||||
|
|
|
@ -163,6 +163,7 @@ impl<'a, 'ctx> Scope<'a, 'ctx> {
|
||||||
pub enum LlvmBackendMode {
|
pub enum LlvmBackendMode {
|
||||||
/// Assumes primitives (roc_alloc, roc_panic, etc) are provided by the host
|
/// Assumes primitives (roc_alloc, roc_panic, etc) are provided by the host
|
||||||
Binary,
|
Binary,
|
||||||
|
BinaryDev,
|
||||||
/// Creates a test wrapper around the main roc function to catch and report panics.
|
/// 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)
|
/// Provides a testing implementation of primitives (roc_alloc, roc_panic, etc)
|
||||||
GenTest,
|
GenTest,
|
||||||
|
@ -174,6 +175,7 @@ impl LlvmBackendMode {
|
||||||
pub(crate) fn has_host(self) -> bool {
|
pub(crate) fn has_host(self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
LlvmBackendMode::Binary => true,
|
LlvmBackendMode::Binary => true,
|
||||||
|
LlvmBackendMode::BinaryDev => true,
|
||||||
LlvmBackendMode::GenTest => false,
|
LlvmBackendMode::GenTest => false,
|
||||||
LlvmBackendMode::WasmGenTest => true,
|
LlvmBackendMode::WasmGenTest => true,
|
||||||
LlvmBackendMode::CliTest => false,
|
LlvmBackendMode::CliTest => false,
|
||||||
|
@ -184,6 +186,7 @@ impl LlvmBackendMode {
|
||||||
fn returns_roc_result(self) -> bool {
|
fn returns_roc_result(self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
LlvmBackendMode::Binary => false,
|
LlvmBackendMode::Binary => false,
|
||||||
|
LlvmBackendMode::BinaryDev => false,
|
||||||
LlvmBackendMode::GenTest => true,
|
LlvmBackendMode::GenTest => true,
|
||||||
LlvmBackendMode::WasmGenTest => true,
|
LlvmBackendMode::WasmGenTest => true,
|
||||||
LlvmBackendMode::CliTest => true,
|
LlvmBackendMode::CliTest => true,
|
||||||
|
@ -193,6 +196,7 @@ impl LlvmBackendMode {
|
||||||
fn runs_expects(self) -> bool {
|
fn runs_expects(self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
LlvmBackendMode::Binary => false,
|
LlvmBackendMode::Binary => false,
|
||||||
|
LlvmBackendMode::BinaryDev => true,
|
||||||
LlvmBackendMode::GenTest => false,
|
LlvmBackendMode::GenTest => false,
|
||||||
LlvmBackendMode::WasmGenTest => false,
|
LlvmBackendMode::WasmGenTest => false,
|
||||||
LlvmBackendMode::CliTest => true,
|
LlvmBackendMode::CliTest => true,
|
||||||
|
@ -2824,6 +2828,10 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
||||||
lookups,
|
lookups,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if let LlvmBackendMode::BinaryDev = env.mode {
|
||||||
|
crate::llvm::expect::finalize(env);
|
||||||
|
}
|
||||||
|
|
||||||
bd.build_unconditional_branch(then_block);
|
bd.build_unconditional_branch(then_block);
|
||||||
}
|
}
|
||||||
roc_target::PtrWidth::Bytes4 => {
|
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
|
// 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()
|
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();
|
let size: BasicValueEnum = return_type.size_of().unwrap().into();
|
||||||
|
@ -4948,7 +4958,7 @@ pub fn build_proc<'a, 'ctx, 'env>(
|
||||||
GenTest | WasmGenTest | CliTest => {
|
GenTest | WasmGenTest | CliTest => {
|
||||||
/* no host, or exposing types is not supported */
|
/* no host, or exposing types is not supported */
|
||||||
}
|
}
|
||||||
Binary => {
|
Binary | BinaryDev => {
|
||||||
for (alias_name, (generated_function, top_level, layout)) in aliases.iter() {
|
for (alias_name, (generated_function, top_level, layout)) in aliases.iter() {
|
||||||
expose_alias_to_host(
|
expose_alias_to_host(
|
||||||
env,
|
env,
|
||||||
|
|
|
@ -14,7 +14,8 @@ use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
|
||||||
use roc_region::all::Region;
|
use roc_region::all::Region;
|
||||||
|
|
||||||
use super::build::{
|
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)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
@ -93,6 +94,16 @@ fn write_state<'a, 'ctx, 'env>(
|
||||||
env.builder.build_store(offset_ptr, offset);
|
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>(
|
pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
scope: &Scope<'a, 'ctx>,
|
scope: &Scope<'a, 'ctx>,
|
||||||
|
@ -101,10 +112,13 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
|
||||||
region: Region,
|
region: Region,
|
||||||
lookups: &[Symbol],
|
lookups: &[Symbol],
|
||||||
) {
|
) {
|
||||||
let func = env
|
let start_function = if let LlvmBackendMode::BinaryDev = env.mode {
|
||||||
.module
|
bitcode::UTILS_EXPECT_FAILED_START_SHARED_FILE
|
||||||
.get_function(bitcode::UTILS_EXPECT_FAILED_START)
|
} else {
|
||||||
.unwrap();
|
bitcode::UTILS_EXPECT_FAILED_START_SHARED_BUFFER
|
||||||
|
};
|
||||||
|
|
||||||
|
let func = env.module.get_function(start_function).unwrap();
|
||||||
|
|
||||||
let call_result = env
|
let call_result = env
|
||||||
.builder
|
.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)
|
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<'_, '_, '_>) {
|
pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
|
||||||
let ctx = env.context;
|
let ctx = env.context;
|
||||||
let module = env.module;
|
let module = env.module;
|
||||||
|
|
|
@ -120,7 +120,11 @@ macro_rules! run_jit_function {
|
||||||
|
|
||||||
$transform(success)
|
$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::docs;
|
||||||
pub use roc_load_internal::file::{
|
pub use roc_load_internal::file::{
|
||||||
EntryPoint, ExecutionMode, Expectations, LoadConfig, LoadResult, LoadStart, LoadedModule,
|
EntryPoint, ExecutionMode, ExpectMetadata, Expectations, LoadConfig, LoadResult, LoadStart,
|
||||||
LoadingProblem, MonomorphizedModule, Phase, Threading,
|
LoadedModule, LoadingProblem, MonomorphizedModule, Phase, Threading,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
|
|
@ -705,6 +705,13 @@ pub struct MonomorphizedModule<'a> {
|
||||||
pub expectations: VecMap<ModuleId, Expectations>,
|
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)]
|
#[derive(Debug)]
|
||||||
pub enum EntryPoint<'a> {
|
pub enum EntryPoint<'a> {
|
||||||
Executable {
|
Executable {
|
||||||
|
|
|
@ -615,6 +615,14 @@ impl<'a> CommentOrNewline<'a> {
|
||||||
DocComment(comment_str) => format!("##{}", comment_str),
|
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)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
|
|
@ -56,13 +56,13 @@ pub struct ExprParseOptions {
|
||||||
/// This is usually true, but false within list/record literals
|
/// This is usually true, but false within list/record literals
|
||||||
/// because the comma separating backpassing arguments conflicts
|
/// because the comma separating backpassing arguments conflicts
|
||||||
/// with the comma separating literal elements
|
/// 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
|
/// Check for the `->` token, and raise an error if found
|
||||||
/// This is usually true, but false in if-guards
|
/// This is usually true, but false in if-guards
|
||||||
///
|
///
|
||||||
/// > Just foo if foo == 2 -> ...
|
/// > Just foo if foo == 2 -> ...
|
||||||
check_for_arrow: bool,
|
pub check_for_arrow: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ExprParseOptions {
|
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>(
|
fn parse_defs_end<'a>(
|
||||||
_options: ExprParseOptions,
|
_options: ExprParseOptions,
|
||||||
min_indent: u32,
|
min_indent: u32,
|
||||||
|
@ -920,94 +979,64 @@ fn parse_defs_end<'a>(
|
||||||
Either::Second(value_def) => {
|
Either::Second(value_def) => {
|
||||||
// If we got a ValueDef::Body, check if a type annotation preceded it.
|
// If we got a ValueDef::Body, check if a type annotation preceded it.
|
||||||
// If so, we may need to combine them into an AnnotatedBody.
|
// 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)
|
ValueDef::Body(loc_pattern, loc_def_expr)
|
||||||
if spaces_before_current.len() <= 1 =>
|
if spaces_before_current.len() <= 1 =>
|
||||||
{
|
{
|
||||||
let region =
|
let region =
|
||||||
Region::span_across(&loc_pattern.region, &loc_def_expr.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() {
|
match defs.last() {
|
||||||
Some(Err(ValueDef::Annotation(ann_pattern, ann_type))) => {
|
Some(Err(ValueDef::Annotation(ann_pattern, ann_type))) => {
|
||||||
// join this body with the preceding annotation
|
let (value_def, region) = join_ann_to_body!(
|
||||||
|
arena,
|
||||||
let value_def = ValueDef::AnnotatedBody {
|
loc_pattern,
|
||||||
ann_pattern: arena.alloc(*ann_pattern),
|
loc_def_expr,
|
||||||
ann_type: arena.alloc(*ann_type),
|
ann_pattern,
|
||||||
comment,
|
ann_type,
|
||||||
body_pattern: arena.alloc(loc_pattern),
|
spaces_before_current,
|
||||||
body_expr: arena.alloc(loc_def_expr),
|
region
|
||||||
};
|
);
|
||||||
|
|
||||||
let region =
|
|
||||||
Region::span_across(&ann_pattern.region, ®ion);
|
|
||||||
|
|
||||||
defs.replace_with_value_def(
|
defs.replace_with_value_def(
|
||||||
defs.tags.len() - 1,
|
defs.tags.len() - 1,
|
||||||
value_def,
|
value_def,
|
||||||
region,
|
region,
|
||||||
)
|
);
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
Some(Ok(TypeDef::Alias {
|
Some(Ok(TypeDef::Alias {
|
||||||
header,
|
header,
|
||||||
ann: ann_type,
|
ann: ann_type,
|
||||||
})) => {
|
})) => {
|
||||||
// This is a case like
|
let (value_def, region) = join_alias_to_body!(
|
||||||
// UserId x : [UserId Int]
|
arena,
|
||||||
// UserId x = UserId 42
|
loc_pattern,
|
||||||
// We optimistically parsed the first line as an alias; we now turn it
|
loc_def_expr,
|
||||||
// into an annotation.
|
header,
|
||||||
|
ann_type,
|
||||||
let loc_name =
|
spaces_before_current,
|
||||||
arena.alloc(header.name.map(|x| Pattern::Tag(x)));
|
region
|
||||||
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,
|
|
||||||
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.replace_with_value_def(
|
||||||
defs.tags.len() - 1,
|
defs.tags.len() - 1,
|
||||||
value_def,
|
value_def,
|
||||||
region,
|
region,
|
||||||
)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// the previous and current def can't be joined up
|
|
||||||
defs.push_value_def(
|
|
||||||
value_def,
|
|
||||||
region,
|
|
||||||
spaces_before_current,
|
|
||||||
&[],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => false,
|
||||||
// the previous and current def can't be joined up
|
};
|
||||||
defs.push_value_def(value_def, region, spaces_before_current, &[]);
|
|
||||||
}
|
if !joined {
|
||||||
|
// the previous and current def can't be joined up
|
||||||
|
defs.push_value_def(value_def, region, spaces_before_current, &[]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1021,9 +1050,9 @@ fn parse_defs_end<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SingleDef<'a> {
|
pub struct SingleDef<'a> {
|
||||||
type_or_value: Either<TypeDef<'a>, ValueDef<'a>>,
|
pub type_or_value: Either<TypeDef<'a>, ValueDef<'a>>,
|
||||||
region: Region,
|
pub region: Region,
|
||||||
spaces_before: &'a [CommentOrNewline<'a>],
|
pub spaces_before: &'a [CommentOrNewline<'a>],
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_defs_expr<'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)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum ExtensionTypeKind {
|
pub enum ExtensionTypeKind {
|
||||||
Record,
|
Record,
|
||||||
|
|
|
@ -244,6 +244,7 @@ fn create_llvm_module<'a>(
|
||||||
};
|
};
|
||||||
let (main_fn_name, main_fn) = match config.mode {
|
let (main_fn_name, main_fn) = match config.mode {
|
||||||
LlvmBackendMode::Binary => unreachable!(),
|
LlvmBackendMode::Binary => unreachable!(),
|
||||||
|
LlvmBackendMode::BinaryDev => unreachable!(),
|
||||||
LlvmBackendMode::CliTest => unreachable!(),
|
LlvmBackendMode::CliTest => unreachable!(),
|
||||||
LlvmBackendMode::WasmGenTest => roc_gen_llvm::llvm::build::build_wasm_test_wrapper(
|
LlvmBackendMode::WasmGenTest => roc_gen_llvm::llvm::build::build_wasm_test_wrapper(
|
||||||
&env,
|
&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;
|
let mut expecting_code_block = false;
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,10 @@ a {
|
||||||
color: #972395;
|
color: #972395;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table tr th, table tr td {
|
||||||
|
padding: 6px 13px;
|
||||||
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
padding: 2px 8px;
|
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;
|
let address = sym.address() as u64;
|
||||||
|
|
||||||
// special exceptions for memcpy and memset.
|
// special exceptions for roc_ functions that map to libc symbols
|
||||||
if name == "roc_memcpy" {
|
let direct_mapping = match name {
|
||||||
vaddresses.insert("memcpy".to_string(), address);
|
"roc_memcpy" => Some("memcpy"),
|
||||||
} else if name == "roc_memset" {
|
"roc_memset" => Some("memset"),
|
||||||
vaddresses.insert("memset".to_string(), address);
|
"roc_memmove" => Some("memmove"),
|
||||||
} else if name == "roc_memmove" {
|
|
||||||
vaddresses.insert("memmove".to_string(), address);
|
// 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);
|
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 = {git = "https://github.com/roc-lang/rustyline", rev = "e74333c"}
|
||||||
rustyline-derive = {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"
|
target-lexicon = "0.12.2"
|
||||||
|
unicode-segmentation = "1.10.0"
|
||||||
|
|
||||||
roc_build = {path = "../compiler/build"}
|
roc_build = {path = "../compiler/build"}
|
||||||
roc_builtins = {path = "../compiler/builtins"}
|
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).
|
//! 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 const_format::concatcp;
|
||||||
use inkwell::context::Context;
|
use repl_state::ReplState;
|
||||||
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 roc_build::link::llvm_module_to_dylib;
|
use crate::repl_state::PROMPT;
|
||||||
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";
|
|
||||||
|
|
||||||
pub const WELCOME_MESSAGE: &str = concatcp!(
|
pub const WELCOME_MESSAGE: &str = concatcp!(
|
||||||
"\n The rockin’ ",
|
"\n The rockin’ ",
|
||||||
|
@ -50,480 +25,54 @@ pub const WELCOME_MESSAGE: &str = concatcp!(
|
||||||
// TODO add link to repl tutorial(does not yet exist).
|
// 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";
|
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 fn main() -> i32 {
|
||||||
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<()> {
|
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::error::ReadlineError;
|
||||||
use rustyline::Editor;
|
use rustyline::Editor;
|
||||||
|
|
||||||
// To debug rustyline:
|
// To debug rustyline:
|
||||||
// <UNCOMMENT> env_logger::init();
|
// <UNCOMMENT> env_logger::init();
|
||||||
// <RUN WITH:> RUST_LOG=rustyline=debug cargo run repl 2> debug.log
|
// <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::<ReplState>::new();
|
||||||
let mut editor = Editor::<ReplHelper>::new();
|
let repl_helper = ReplState::new();
|
||||||
let repl_helper = ReplHelper::new();
|
|
||||||
editor.set_helper(Some(repl_helper));
|
editor.set_helper(Some(repl_helper));
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let readline = editor.readline(PROMPT);
|
match editor.readline(PROMPT) {
|
||||||
|
|
||||||
match readline {
|
|
||||||
Ok(line) => {
|
Ok(line) => {
|
||||||
let trim_line = line.trim();
|
editor.add_history_entry(line.trim());
|
||||||
editor.add_history_entry(trim_line);
|
|
||||||
|
|
||||||
let pending_src = &mut editor
|
let dimensions = editor.dimensions();
|
||||||
.helper_mut()
|
let repl_helper = editor.helper_mut().expect("Editor helper was not set");
|
||||||
.expect("Editor helper was not set")
|
|
||||||
.pending_src;
|
|
||||||
|
|
||||||
match trim_line.to_lowercase().as_str() {
|
match repl_helper.step(&line, dimensions) {
|
||||||
"" => {
|
Ok(output) => {
|
||||||
if pending_src.is_empty() {
|
// If there was no output, don't print a blank line!
|
||||||
print!("\n{}", SHORT_INSTRUCTIONS);
|
// (This happens for something like a type annotation.)
|
||||||
} else if prev_line_blank {
|
if !output.is_empty() {
|
||||||
// After two blank lines in a row, give up and try parsing it
|
println!("{}", output);
|
||||||
// even though it's going to fail. This way you don't get stuck.
|
|
||||||
match eval_and_format(pending_src.as_str()) {
|
|
||||||
Ok(output) => {
|
|
||||||
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" => {
|
Err(exit_code) => return exit_code,
|
||||||
// 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())
|
|
||||||
};
|
|
||||||
|
|
||||||
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) => {
|
#[cfg(windows)]
|
||||||
println!("CTRL-C");
|
Err(ReadlineError::WindowResize) => {
|
||||||
break;
|
// This is fine; just ignore it.
|
||||||
}
|
}
|
||||||
Err(ReadlineError::Eof) => {
|
Err(ReadlineError::Eof) => {
|
||||||
// If we hit an eof, and we're allowed to keep going,
|
// End of input; we're done!
|
||||||
// append the str to the src we're building up and continue.
|
return 0;
|
||||||
// (We only need to append it here if it was empty before;
|
}
|
||||||
// otherwise, we already appended it before calling eval_and_format.)
|
Err(ReadlineError::Interrupted) => {
|
||||||
let pending_src = &mut editor
|
eprintln!("CTRL-C");
|
||||||
.helper_mut()
|
return 1;
|
||||||
.expect("Editor helper was not set")
|
|
||||||
.pending_src;
|
|
||||||
|
|
||||||
if pending_src.is_empty() {
|
|
||||||
pending_src.push_str("");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("REPL error: {:?}", 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
|
||||||
|
}
|
|
@ -24,4 +24,4 @@ roc_region = {path = "../compiler/region"}
|
||||||
roc_reporting = {path = "../reporting"}
|
roc_reporting = {path = "../reporting"}
|
||||||
roc_std = {path = "../roc_std"}
|
roc_std = {path = "../roc_std"}
|
||||||
roc_target = {path = "../compiler/roc_target"}
|
roc_target = {path = "../compiler/roc_target"}
|
||||||
roc_types = {path = "../compiler/types"}
|
roc_types = {path = "../compiler/types"}
|
|
@ -13,7 +13,7 @@ use roc_mono::layout::{
|
||||||
self, union_sorted_tags_pub, Builtin, Layout, LayoutCache, LayoutInterner, UnionLayout,
|
self, union_sorted_tags_pub, Builtin, Layout, LayoutCache, LayoutInterner, UnionLayout,
|
||||||
UnionVariant, WrappedVariant,
|
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_region::all::{Loc, Region};
|
||||||
use roc_std::RocDec;
|
use roc_std::RocDec;
|
||||||
use roc_target::TargetInfo;
|
use roc_target::TargetInfo;
|
||||||
|
@ -29,11 +29,6 @@ struct Env<'a, 'env> {
|
||||||
layout_cache: LayoutCache<'a>,
|
layout_cache: LayoutCache<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum ToAstProblem {
|
|
||||||
FunctionLayout,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// JIT execute the given main function, and then wrap its results in an Expr
|
/// 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.
|
/// 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,
|
interns: &'a Interns,
|
||||||
layout_interner: LayoutInterner<'a>,
|
layout_interner: LayoutInterner<'a>,
|
||||||
target_info: TargetInfo,
|
target_info: TargetInfo,
|
||||||
) -> Result<Expr<'a>, ToAstProblem> {
|
) -> Expr<'a> {
|
||||||
let mut env = Env {
|
let mut env = Env {
|
||||||
arena,
|
arena,
|
||||||
subs,
|
subs,
|
||||||
|
@ -68,10 +63,24 @@ pub fn jit_to_ast<'a, A: ReplApp<'a>>(
|
||||||
result,
|
result,
|
||||||
captures_niche: _,
|
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)
|
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,
|
main_fn_name: &str,
|
||||||
layout: &Layout<'a>,
|
layout: &Layout<'a>,
|
||||||
var: Variable,
|
var: Variable,
|
||||||
) -> Result<Expr<'a>, ToAstProblem> {
|
) -> Expr<'a> {
|
||||||
let (newtype_containers, alias_content, raw_var) = unroll_newtypes_and_aliases(env, var);
|
let (newtype_containers, alias_content, raw_var) = unroll_newtypes_and_aliases(env, var);
|
||||||
|
|
||||||
macro_rules! num_helper {
|
macro_rules! num_helper {
|
||||||
|
@ -342,18 +351,17 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = match layout {
|
let expr = match layout {
|
||||||
Layout::Builtin(Builtin::Bool) => Ok(app.call_function(
|
Layout::Builtin(Builtin::Bool) => {
|
||||||
main_fn_name,
|
app.call_function(main_fn_name, |_mem: &A::Memory, num: bool| {
|
||||||
|_mem: &A::Memory, num: bool| {
|
|
||||||
bool_to_ast(env, num, env.subs.get_content_without_compacting(raw_var))
|
bool_to_ast(env, num, env.subs.get_content_without_compacting(raw_var))
|
||||||
},
|
})
|
||||||
)),
|
}
|
||||||
Layout::Builtin(Builtin::Int(int_width)) => {
|
Layout::Builtin(Builtin::Int(int_width)) => {
|
||||||
use Content::*;
|
use Content::*;
|
||||||
use IntWidth::*;
|
use IntWidth::*;
|
||||||
|
|
||||||
let result = match (alias_content, int_width) {
|
match (alias_content, int_width) {
|
||||||
(Some(Alias(Symbol::NUM_UNSIGNED8, ..)), U8) => num_helper!(u8),
|
(Some(Alias(Symbol::NUM_UNSIGNED8, ..)), U8) => num_helper!(u8),
|
||||||
(_, U8) => {
|
(_, U8) => {
|
||||||
// This is not a number, it's a tag union or something else
|
// 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),
|
(_, I32) => num_helper!(i32),
|
||||||
(_, I64) => num_helper!(i64),
|
(_, I64) => num_helper!(i64),
|
||||||
(_, I128) => num_helper!(i128),
|
(_, I128) => num_helper!(i128),
|
||||||
};
|
}
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
}
|
||||||
Layout::Builtin(Builtin::Float(float_width)) => {
|
Layout::Builtin(Builtin::Float(float_width)) => {
|
||||||
use FloatWidth::*;
|
use FloatWidth::*;
|
||||||
|
|
||||||
let result = match float_width {
|
match float_width {
|
||||||
F32 => num_helper!(f32),
|
F32 => num_helper!(f32),
|
||||||
F64 => num_helper!(f64),
|
F64 => num_helper!(f64),
|
||||||
F128 => todo!("F128 not implemented"),
|
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) => {
|
Layout::Builtin(Builtin::Str) => {
|
||||||
let body = |mem: &A::Memory, addr| {
|
let body = |mem: &A::Memory, addr| {
|
||||||
let string = mem.deref_str(addr);
|
let string = mem.deref_str(addr);
|
||||||
|
@ -394,24 +398,21 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
||||||
Expr::Str(StrLiteral::PlainLine(arena_str))
|
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(
|
|
||||||
main_fn_name,
|
|
||||||
|mem: &A::Memory, (addr, len, _cap)| {
|
|
||||||
list_to_ast(
|
|
||||||
env,
|
|
||||||
mem,
|
|
||||||
addr,
|
|
||||||
len,
|
|
||||||
elem_layout,
|
|
||||||
env.subs.get_content_without_compacting(raw_var),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
Layout::Builtin(Builtin::List(elem_layout)) => app.call_function_returns_roc_list(
|
||||||
|
main_fn_name,
|
||||||
|
|mem: &A::Memory, (addr, len, _cap)| {
|
||||||
|
list_to_ast(
|
||||||
|
env,
|
||||||
|
mem,
|
||||||
|
addr,
|
||||||
|
len,
|
||||||
|
elem_layout,
|
||||||
|
env.subs.get_content_without_compacting(raw_var),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
Layout::Struct { field_layouts, .. } => {
|
Layout::Struct { field_layouts, .. } => {
|
||||||
let fields = [Layout::u64(), *layout];
|
let fields = [Layout::u64(), *layout];
|
||||||
let layout = Layout::struct_no_name_order(env.arena.alloc(fields));
|
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)
|
.get_content_without_compacting(raw_var)
|
||||||
{
|
{
|
||||||
Content::Structure(FlatType::Record(fields, _)) => {
|
Content::Structure(FlatType::Record(fields, _)) => {
|
||||||
Ok(struct_to_ast(env, mem, addr, *fields))
|
struct_to_ast(env, mem, addr, *fields)
|
||||||
}
|
}
|
||||||
Content::Structure(FlatType::EmptyRecord) => {
|
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, _)) => {
|
Content::Structure(FlatType::TagUnion(tags, _)) => {
|
||||||
let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags);
|
let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags);
|
||||||
|
|
||||||
Ok(single_tag_union_to_ast(
|
single_tag_union_to_ast(env, mem, addr, field_layouts, tag_name, payload_vars)
|
||||||
env,
|
|
||||||
mem,
|
|
||||||
addr,
|
|
||||||
field_layouts,
|
|
||||||
tag_name,
|
|
||||||
payload_vars,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
Content::Structure(FlatType::FunctionOrTagUnion(tag_names, _, _)) => {
|
Content::Structure(FlatType::FunctionOrTagUnion(tag_names, _, _)) => {
|
||||||
let tag_name = &env.subs.get_subs_slice(*tag_names)[0];
|
let tag_name = &env.subs.get_subs_slice(*tag_names)[0];
|
||||||
|
|
||||||
Ok(single_tag_union_to_ast(
|
single_tag_union_to_ast(env, mem, addr, field_layouts, tag_name, &[])
|
||||||
env,
|
|
||||||
mem,
|
|
||||||
addr,
|
|
||||||
field_layouts,
|
|
||||||
tag_name,
|
|
||||||
&[],
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
Content::Structure(FlatType::Func(_, _, _)) => {
|
Content::Structure(FlatType::Func(_, _, _)) => {
|
||||||
// a function with a struct as the closure environment
|
// a function with a struct as the closure environment
|
||||||
Ok(OPAQUE_FUNCTION)
|
OPAQUE_FUNCTION
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
unreachable!(
|
unreachable!(
|
||||||
|
@ -472,7 +459,8 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
||||||
}
|
}
|
||||||
Layout::Union(UnionLayout::NonRecursive(_)) => {
|
Layout::Union(UnionLayout::NonRecursive(_)) => {
|
||||||
let size = layout.stack_size(&env.layout_cache.interner, env.target_info);
|
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,
|
main_fn_name,
|
||||||
size as usize,
|
size as usize,
|
||||||
|mem: &'a A::Memory, addr: 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),
|
env.subs.get_root_key_without_compacting(raw_var),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
Layout::Union(UnionLayout::Recursive(_))
|
Layout::Union(UnionLayout::Recursive(_))
|
||||||
| Layout::Union(UnionLayout::NonNullableUnwrapped(_))
|
| Layout::Union(UnionLayout::NonNullableUnwrapped(_))
|
||||||
| Layout::Union(UnionLayout::NullableUnwrapped { .. })
|
| Layout::Union(UnionLayout::NullableUnwrapped { .. })
|
||||||
| Layout::Union(UnionLayout::NullableWrapped { .. }) => {
|
| Layout::Union(UnionLayout::NullableWrapped { .. }) => {
|
||||||
let size = layout.stack_size(&env.layout_cache.interner, env.target_info);
|
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,
|
main_fn_name,
|
||||||
size as usize,
|
size as usize,
|
||||||
|mem: &'a A::Memory, addr: usize| {
|
|mem: &'a A::Memory, addr: usize| {
|
||||||
|
@ -505,31 +494,29 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
||||||
env.subs.get_root_key_without_compacting(raw_var),
|
env.subs.get_root_key_without_compacting(raw_var),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
Layout::RecursivePointer => {
|
Layout::RecursivePointer => {
|
||||||
unreachable!("RecursivePointers can only be inside structures")
|
unreachable!("RecursivePointers can only be inside structures")
|
||||||
}
|
}
|
||||||
Layout::LambdaSet(_) => Ok(OPAQUE_FUNCTION),
|
Layout::LambdaSet(_) => OPAQUE_FUNCTION,
|
||||||
Layout::Boxed(_) => {
|
Layout::Boxed(_) => {
|
||||||
let size = layout.stack_size(&env.layout_cache.interner, env.target_info);
|
let size = layout.stack_size(&env.layout_cache.interner, env.target_info);
|
||||||
Ok(app.call_function_dynamic_size(
|
|
||||||
main_fn_name,
|
app.call_function_dynamic_size(main_fn_name, size as usize, |mem: &A::Memory, addr| {
|
||||||
size as usize,
|
addr_to_ast(
|
||||||
|mem: &A::Memory, addr| {
|
env,
|
||||||
addr_to_ast(
|
mem,
|
||||||
env,
|
addr,
|
||||||
mem,
|
layout,
|
||||||
addr,
|
WhenRecursive::Unreachable,
|
||||||
layout,
|
env.subs.get_root_key_without_compacting(raw_var),
|
||||||
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> {
|
fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use roc_load::{ExecutionMode, LoadConfig, Threading};
|
use roc_load::{ExecutionMode, LoadConfig, Threading};
|
||||||
use roc_reporting::report::Palette;
|
use roc_reporting::report::{Palette, Severity};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use roc_fmt::annotation::Formattable;
|
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_reporting::report::{can_problem, type_problem, RocDocAllocator};
|
||||||
use roc_target::TargetInfo;
|
use roc_target::TargetInfo;
|
||||||
|
|
||||||
use crate::eval::ToAstProblem;
|
#[derive(Debug)]
|
||||||
|
pub struct ReplOutput {
|
||||||
pub enum ReplOutput {
|
pub expr: String,
|
||||||
Problems(Vec<String>),
|
pub expr_type: String,
|
||||||
NoProblems { expr: String, expr_type: String },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format_answer(
|
pub fn format_answer<'a>(arena: &'a Bump, answer: Expr<'_>) -> &'a str {
|
||||||
arena: &Bump,
|
match answer {
|
||||||
res_answer: Result<Expr, ToAstProblem>,
|
Expr::Closure(_, _) | Expr::MalformedClosure => "<function>",
|
||||||
expr_type_str: String,
|
_ => {
|
||||||
) -> ReplOutput {
|
let mut expr = roc_fmt::Buf::new_in(arena);
|
||||||
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);
|
answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0);
|
||||||
}
|
|
||||||
Err(FunctionLayout) => {
|
|
||||||
expr.indent(0);
|
|
||||||
expr.push_str("<function>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ReplOutput::NoProblems {
|
expr.into_bump_str()
|
||||||
expr: expr.into_bump_str().to_string(),
|
}
|
||||||
expr_type: expr_type_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,
|
arena: &'a Bump,
|
||||||
src: &str,
|
defs: I,
|
||||||
|
expr: &str,
|
||||||
target_info: TargetInfo,
|
target_info: TargetInfo,
|
||||||
palette: Palette,
|
palette: Palette,
|
||||||
) -> Result<MonomorphizedModule<'a>, Vec<String>> {
|
) -> (Option<MonomorphizedModule<'a>>, Problems) {
|
||||||
let filename = PathBuf::from("");
|
let filename = PathBuf::from("");
|
||||||
let src_dir = PathBuf::from("fake/test/path");
|
let src_dir = PathBuf::from("fake/test/path");
|
||||||
|
let (bytes_before_expr, module_src) = promote_expr_to_module(arena, defs, expr);
|
||||||
let module_src = arena.alloc(promote_expr_to_module(src));
|
|
||||||
|
|
||||||
let exposed_types = Default::default();
|
let exposed_types = Default::default();
|
||||||
let loaded = roc_load::load_and_monomorphize_from_str(
|
let loaded = roc_load::load_and_monomorphize_from_str(
|
||||||
arena,
|
arena,
|
||||||
|
@ -71,10 +70,16 @@ pub fn compile_to_mono<'a>(
|
||||||
let mut loaded = match loaded {
|
let mut loaded = match loaded {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(LoadingProblem::FormattedReport(report)) => {
|
Err(LoadingProblem::FormattedReport(report)) => {
|
||||||
return Err(vec![report]);
|
return (
|
||||||
|
None,
|
||||||
|
Problems {
|
||||||
|
errors: vec![report],
|
||||||
|
warnings: Vec::new(),
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
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;
|
} = &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() {
|
for (home, (module_path, src)) in sources.iter() {
|
||||||
let can_probs = can_problems.remove(home).unwrap_or_default();
|
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);
|
let alloc = RocDocAllocator::new(&src_lines, *home, interns);
|
||||||
|
|
||||||
for problem in can_probs.into_iter() {
|
for problem in can_probs.into_iter() {
|
||||||
let report = can_problem(&alloc, &line_info, module_path.clone(), problem);
|
// Filter out all warnings and errors whose regions end before this,
|
||||||
let mut buf = String::new();
|
// 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.
|
||||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
// This filters out things like shadowing warnings and unused def warnings.
|
||||||
|
if problem.region().unwrap_or_default().end().offset as usize >= bytes_before_expr {
|
||||||
lines.push(buf);
|
let report = can_problem(&alloc, &line_info, module_path.clone(), problem);
|
||||||
}
|
let severity = report.severity;
|
||||||
|
|
||||||
for problem in type_probs {
|
|
||||||
if let Some(report) = type_problem(&alloc, &line_info, module_path.clone(), problem) {
|
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
|
|
||||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
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);
|
||||||
|
|
||||||
|
match severity {
|
||||||
|
Severity::Warning => {
|
||||||
|
warnings.push(buf);
|
||||||
|
}
|
||||||
|
Severity::RuntimeError => {
|
||||||
|
errors.push(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !lines.is_empty() {
|
(Some(loaded), problems)
|
||||||
Err(lines)
|
|
||||||
} else {
|
|
||||||
Ok(loaded)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn promote_expr_to_module(src: &str) -> String {
|
fn promote_expr_to_module<'a, 'i, I: Iterator<Item = &'i str>>(
|
||||||
let mut buffer =
|
arena: &'a Bump,
|
||||||
String::from("app \"app\" provides [replOutput] to \"./platform\"\n\nreplOutput =\n");
|
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() {
|
let mut buffer = bumpalo::collections::string::String::from_str_in(REPL_MODULE_HEADER, arena);
|
||||||
// indent the body!
|
|
||||||
buffer.push_str(" ");
|
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_str(line);
|
||||||
buffer.push('\n');
|
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_gen_llvm = {path = "../compiler/gen_llvm"}
|
||||||
roc_region = { path = "../compiler/region" }
|
roc_region = { path = "../compiler/region" }
|
||||||
roc_build = { path = "../compiler/build" }
|
roc_build = { path = "../compiler/build" }
|
||||||
|
roc_error_macros = { path = "../error_macros" }
|
||||||
|
|
||||||
libloading = "0.7.1"
|
libloading = "0.7.1"
|
||||||
inkwell = { path = "../vendor/inkwell" }
|
inkwell = { path = "../vendor/inkwell" }
|
||||||
|
|
|
@ -8,10 +8,7 @@ use {
|
||||||
layout::{CapturesNiche, Layout, LayoutCache},
|
layout::{CapturesNiche, Layout, LayoutCache},
|
||||||
},
|
},
|
||||||
roc_parse::ast::Expr,
|
roc_parse::ast::Expr,
|
||||||
roc_repl_eval::{
|
roc_repl_eval::{eval::jit_to_ast, ReplAppMemory},
|
||||||
eval::{jit_to_ast, ToAstProblem},
|
|
||||||
ReplAppMemory,
|
|
||||||
},
|
|
||||||
roc_target::TargetInfo,
|
roc_target::TargetInfo,
|
||||||
roc_types::subs::{Subs, Variable},
|
roc_types::subs::{Subs, Variable},
|
||||||
std::sync::Arc,
|
std::sync::Arc,
|
||||||
|
@ -36,7 +33,7 @@ pub fn get_values<'a>(
|
||||||
start: *const u8,
|
start: *const u8,
|
||||||
start_offset: usize,
|
start_offset: usize,
|
||||||
variables: &[Variable],
|
variables: &[Variable],
|
||||||
) -> Result<(usize, Vec<Expr<'a>>), ToAstProblem> {
|
) -> (usize, Vec<Expr<'a>>) {
|
||||||
let mut result = Vec::with_capacity(variables.len());
|
let mut result = Vec::with_capacity(variables.len());
|
||||||
|
|
||||||
let memory = ExpectMemory { start };
|
let memory = ExpectMemory { start };
|
||||||
|
@ -75,13 +72,13 @@ pub fn get_values<'a>(
|
||||||
interns,
|
interns,
|
||||||
layout_interner.fork(),
|
layout_interner.fork(),
|
||||||
target_info,
|
target_info,
|
||||||
)?
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
result.push(expr);
|
result.push(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((app.offset, result))
|
(app.offset, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
|
|
|
@ -6,6 +6,7 @@ use inkwell::context::Context;
|
||||||
use roc_build::link::llvm_module_to_dylib;
|
use roc_build::link::llvm_module_to_dylib;
|
||||||
use roc_can::expr::ExpectLookup;
|
use roc_can::expr::ExpectLookup;
|
||||||
use roc_collections::{MutSet, VecMap};
|
use roc_collections::{MutSet, VecMap};
|
||||||
|
use roc_error_macros::internal_error;
|
||||||
use roc_gen_llvm::{
|
use roc_gen_llvm::{
|
||||||
llvm::{build::LlvmBackendMode, externs::add_default_roc_externs},
|
llvm::{build::LlvmBackendMode, externs::add_default_roc_externs},
|
||||||
run_roc::RocCallResult,
|
run_roc::RocCallResult,
|
||||||
|
@ -21,7 +22,7 @@ use roc_target::TargetInfo;
|
||||||
use roc_types::subs::{Subs, Variable};
|
use roc_types::subs::{Subs, Variable};
|
||||||
use target_lexicon::Triple;
|
use target_lexicon::Triple;
|
||||||
|
|
||||||
pub(crate) struct ExpectMemory<'a> {
|
pub struct ExpectMemory<'a> {
|
||||||
ptr: *mut u8,
|
ptr: *mut u8,
|
||||||
length: usize,
|
length: usize,
|
||||||
shm_name: Option<std::ffi::CString>,
|
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();
|
let cstring = std::ffi::CString::new(shm_name).unwrap();
|
||||||
Self::mmap_help(cstring, libc::O_RDWR | libc::O_CREAT)
|
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 {
|
fn mmap_help(cstring: std::ffi::CString, shm_flags: i32) -> Self {
|
||||||
let ptr = unsafe {
|
let ptr = unsafe {
|
||||||
let shared_fd = libc::shm_open(cstring.as_ptr().cast(), shm_flags, 0o666);
|
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(),
|
std::ptr::null_mut(),
|
||||||
Self::SHM_SIZE,
|
Self::SHM_SIZE,
|
||||||
libc::PROT_WRITE | libc::PROT_READ,
|
libc::PROT_WRITE | libc::PROT_READ,
|
||||||
libc::MAP_SHARED,
|
libc::MAP_SHARED,
|
||||||
shared_fd,
|
shared_fd,
|
||||||
0,
|
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 {
|
Self {
|
||||||
ptr: ptr.cast(),
|
ptr: ptr.cast(),
|
||||||
length: Self::SHM_SIZE,
|
length: Self::SHM_SIZE,
|
||||||
|
@ -83,7 +103,34 @@ impl<'a> ExpectMemory<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[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,
|
writer: &mut W,
|
||||||
render_target: RenderTarget,
|
render_target: RenderTarget,
|
||||||
arena: &'a Bump,
|
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,
|
writer: &mut impl std::io::Write,
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
expectations: &mut VecMap<ModuleId, Expectations>,
|
expectations: &mut VecMap<ModuleId, Expectations>,
|
||||||
interns: &'a Interns,
|
interns: &'a Interns,
|
||||||
layout_interner: &Arc<GlobalInterner<'a, Layout<'a>>>,
|
layout_interner: &Arc<GlobalInterner<'a, Layout<'a>>>,
|
||||||
shared_ptr: *mut u8,
|
memory: &ExpectMemory,
|
||||||
) -> std::io::Result<usize> {
|
) -> std::io::Result<usize> {
|
||||||
|
let shared_ptr = memory.ptr;
|
||||||
|
|
||||||
let frame = ExpectFrame::at_offset(shared_ptr, ExpectSequence::START_OFFSET);
|
let frame = ExpectFrame::at_offset(shared_ptr, ExpectSequence::START_OFFSET);
|
||||||
let module_id = frame.module_id;
|
let module_id = frame.module_id;
|
||||||
|
|
||||||
|
@ -423,8 +472,7 @@ fn render_expect_failure<'a>(
|
||||||
start,
|
start,
|
||||||
frame.start_offset,
|
frame.start_offset,
|
||||||
&variables,
|
&variables,
|
||||||
)
|
);
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
renderer.render_failure(
|
renderer.render_failure(
|
||||||
writer,
|
writer,
|
||||||
|
|
|
@ -17,6 +17,7 @@ indoc = "1.0.7"
|
||||||
strip-ansi-escapes = "0.1.1"
|
strip-ansi-escapes = "0.1.1"
|
||||||
wasmer-wasi = "2.2.1"
|
wasmer-wasi = "2.2.1"
|
||||||
|
|
||||||
|
roc_build = { path = "../compiler/build" }
|
||||||
roc_repl_cli = {path = "../repl_cli"}
|
roc_repl_cli = {path = "../repl_cli"}
|
||||||
roc_test_utils = {path = "../test_utils"}
|
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"] }
|
wasmer = { version = "2.2.1", default-features = false, features = ["cranelift", "universal"] }
|
||||||
|
|
||||||
[features]
|
[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::path::PathBuf;
|
||||||
use std::process::{Command, ExitStatus, Stdio};
|
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;
|
use roc_test_utils::assert_multiline_str_eq;
|
||||||
|
|
||||||
const ERROR_MESSAGE_START: char = '─';
|
const ERROR_MESSAGE_START: char = '─';
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Out {
|
pub struct Out {
|
||||||
stdout: String,
|
pub stdout: String,
|
||||||
stderr: String,
|
pub stderr: String,
|
||||||
status: ExitStatus,
|
pub status: ExitStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_to_roc_binary() -> PathBuf {
|
fn path_to_roc_binary() -> PathBuf {
|
||||||
|
@ -39,7 +39,7 @@ fn path_to_roc_binary() -> PathBuf {
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
fn repl_eval(input: &str) -> Out {
|
pub fn repl_eval(input: &str) -> Out {
|
||||||
let mut cmd = Command::new(path_to_roc_binary());
|
let mut cmd = Command::new(path_to_roc_binary());
|
||||||
|
|
||||||
cmd.arg("repl");
|
cmd.arg("repl");
|
||||||
|
@ -75,7 +75,7 @@ fn repl_eval(input: &str) -> Out {
|
||||||
|
|
||||||
// Remove the initial instructions from the output.
|
// 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();
|
let stdout = String::from_utf8(output.stdout).unwrap();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
|
@ -124,10 +124,25 @@ fn repl_eval(input: &str) -> Out {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expect_success(input: &str, expected: &str) {
|
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!("", 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());
|
assert!(out.status.success());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,9 @@ extern crate lazy_static;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod state;
|
||||||
|
|
||||||
#[cfg(all(test, not(feature = "wasm")))]
|
#[cfg(all(test, not(feature = "wasm")))]
|
||||||
mod cli;
|
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)]
|
#[allow(unused_imports)]
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
|
use roc_test_utils::assert_multiline_str_eq;
|
||||||
|
|
||||||
#[cfg(not(feature = "wasm"))]
|
#[cfg(not(feature = "wasm"))]
|
||||||
use crate::cli::{expect_failure, expect_success};
|
use crate::cli::{expect_failure, expect_success, repl_eval};
|
||||||
|
|
||||||
#[cfg(feature = "wasm")]
|
#[cfg(feature = "wasm")]
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
@ -199,8 +200,7 @@ fn newtype_of_big_data() {
|
||||||
Either a b : [Left a, Right b]
|
Either a b : [Left a, Right b]
|
||||||
lefty : Either Str Str
|
lefty : Either Str Str
|
||||||
lefty = Left "loosey"
|
lefty = Left "loosey"
|
||||||
A lefty
|
A lefty"#
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
r#"A (Left "loosey") : [A (Either Str Str)]"#,
|
r#"A (Left "loosey") : [A (Either Str Str)]"#,
|
||||||
)
|
)
|
||||||
|
@ -214,8 +214,7 @@ fn newtype_nested() {
|
||||||
Either a b : [Left a, Right b]
|
Either a b : [Left a, Right b]
|
||||||
lefty : Either Str Str
|
lefty : Either Str Str
|
||||||
lefty = Left "loosey"
|
lefty = Left "loosey"
|
||||||
A (B (C lefty))
|
A (B (C lefty))"#
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
r#"A (B (C (Left "loosey"))) : [A [B [C (Either Str Str)]]]"#,
|
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 a : [Big a [Wrapper [Newtype a]]]
|
||||||
big : Big Str
|
big : Big Str
|
||||||
big = Big "s" (Wrapper (Newtype "t"))
|
big = Big "s" (Wrapper (Newtype "t"))
|
||||||
A big
|
A big"#
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
r#"A (Big "s" (Wrapper (Newtype "t"))) : [A (Big Str)]"#,
|
r#"A (Big "s" (Wrapper (Newtype "t"))) : [A (Big Str)]"#,
|
||||||
)
|
)
|
||||||
|
@ -560,17 +558,45 @@ fn four_element_record() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "wasm"))]
|
||||||
#[test]
|
#[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
|
// If a string contains newlines, format it as a multiline string in the output
|
||||||
expect_success(
|
expect_success(
|
||||||
r#""\n\nhi!\n\n""#,
|
r#""\n\nhi!\n\n""#,
|
||||||
indoc!(
|
indoc!(
|
||||||
r#""""
|
r#""""
|
||||||
|
|
||||||
|
|
||||||
hi!
|
hi!
|
||||||
|
|
||||||
|
|
||||||
""" : Str"#
|
""" : Str"#
|
||||||
),
|
),
|
||||||
|
@ -933,39 +959,7 @@ fn parse_problem() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "wasm"))] // TODO: mismatch is due to terminal control codes!
|
#[ignore] // re-enable (and fix) after https://github.com/roc-lang/roc/issues/4425 is done!
|
||||||
#[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!
|
|
||||||
"#
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "wasm"))]
|
#[cfg(not(feature = "wasm"))]
|
||||||
#[test]
|
#[test]
|
||||||
fn issue_2343_complete_mono_with_shadowed_vars() {
|
fn issue_2343_complete_mono_with_shadowed_vars() {
|
||||||
|
@ -1024,8 +1018,7 @@ fn tag_with_type_behind_alias() {
|
||||||
T : [A Str]
|
T : [A Str]
|
||||||
v : T
|
v : T
|
||||||
v = A "value"
|
v = A "value"
|
||||||
v
|
v"#
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
r#"A "value" : T"#,
|
r#"A "value" : T"#,
|
||||||
);
|
);
|
||||||
|
@ -1039,8 +1032,7 @@ fn issue_2588_record_with_function_and_nonfunction() {
|
||||||
r#"
|
r#"
|
||||||
x = 1
|
x = 1
|
||||||
f = \n -> n * 2
|
f = \n -> n * 2
|
||||||
{ y: f x, f }
|
{ y: f x, f }"#
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
r#"{ f: <function>, y: 2 } : { f : Num a -> Num a, y : Num * }"#,
|
r#"{ f: <function>, y: 2 } : { f : Num a -> Num a, y : Num * }"#,
|
||||||
)
|
)
|
||||||
|
@ -1053,8 +1045,7 @@ fn opaque_apply() {
|
||||||
r#"
|
r#"
|
||||||
Age := U32
|
Age := U32
|
||||||
|
|
||||||
@Age 23
|
@Age 23"#
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
"@Age 23 : Age",
|
"@Age 23 : Age",
|
||||||
)
|
)
|
||||||
|
@ -1067,8 +1058,7 @@ fn opaque_apply_polymorphic() {
|
||||||
r#"
|
r#"
|
||||||
F t u := [Package t u]
|
F t u := [Package t u]
|
||||||
|
|
||||||
@F (Package "" { a: "" })
|
@F (Package "" { a: "" })"#
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
r#"@F (Package "" { a: "" }) : F Str { a : Str }"#,
|
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 (Package {} A)
|
||||||
|
|
||||||
f (@F (Package A {}))
|
f (@F (Package A {}))"#
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
r#"@F (Package {} A) : F {} [A]"#,
|
r#"@F (Package {} A) : F {} [A]"#,
|
||||||
)
|
)
|
||||||
|
@ -1097,10 +1086,9 @@ fn dec_in_repl() {
|
||||||
r#"
|
r#"
|
||||||
x: Dec
|
x: Dec
|
||||||
x=1.23
|
x=1.23
|
||||||
x
|
x"#
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
r#"1.23 : Dec"#,
|
"1.23 : Dec",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1111,8 +1099,7 @@ fn print_i8_issue_2710() {
|
||||||
r#"
|
r#"
|
||||||
a : I8
|
a : I8
|
||||||
a = -1
|
a = -1
|
||||||
a
|
a"#
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
r#"-1 : I8"#,
|
r#"-1 : I8"#,
|
||||||
)
|
)
|
||||||
|
@ -1124,8 +1111,7 @@ fn box_box() {
|
||||||
expect_success(
|
expect_success(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
Box.box "container store"
|
Box.box "container store""#
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
r#"Box.box "container store" : Box Str"#,
|
r#"Box.box "container store" : Box Str"#,
|
||||||
)
|
)
|
||||||
|
@ -1140,8 +1126,7 @@ fn box_box_type_alias() {
|
||||||
HeapStr : Box Str
|
HeapStr : Box Str
|
||||||
helloHeap : HeapStr
|
helloHeap : HeapStr
|
||||||
helloHeap = Box.box "bye stacks"
|
helloHeap = Box.box "bye stacks"
|
||||||
helloHeap
|
helloHeap"#
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
r#"Box.box "bye stacks" : HeapStr"#,
|
r#"Box.box "bye stacks" : HeapStr"#,
|
||||||
)
|
)
|
||||||
|
@ -1165,9 +1150,7 @@ fn issue_2818() {
|
||||||
f : {} -> List Str
|
f : {} -> List Str
|
||||||
f = \_ ->
|
f = \_ ->
|
||||||
x = []
|
x = []
|
||||||
x
|
x"#
|
||||||
f
|
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
r"<function> : {} -> List Str",
|
r"<function> : {} -> List Str",
|
||||||
)
|
)
|
||||||
|
@ -1186,8 +1169,7 @@ fn issue_2810_recursive_layout_inside_nonrecursive() {
|
||||||
|
|
||||||
a : Job
|
a : Job
|
||||||
a = Job (Command (FromJob (Job (Command SystemTool))))
|
a = Job (Command (FromJob (Job (Command SystemTool))))
|
||||||
a
|
a"#
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
"Job (Command (FromJob (Job (Command SystemTool)))) : Job",
|
"Job (Command (FromJob (Job (Command SystemTool)))) : Job",
|
||||||
)
|
)
|
||||||
|
@ -1199,14 +1181,10 @@ fn render_nullable_unwrapped_passing_through_alias() {
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
Deep : [L DeepList]
|
Deep : [L DeepList]
|
||||||
|
|
||||||
DeepList : [Nil, Cons Deep]
|
DeepList : [Nil, Cons Deep]
|
||||||
|
|
||||||
v : DeepList
|
v : DeepList
|
||||||
v = (Cons (L (Cons (L (Cons (L Nil))))))
|
v = (Cons (L (Cons (L (Cons (L Nil))))))
|
||||||
|
v"#
|
||||||
v
|
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
"Cons (L (Cons (L (Cons (L Nil))))) : DeepList",
|
"Cons (L (Cons (L (Cons (L Nil))))) : DeepList",
|
||||||
)
|
)
|
||||||
|
@ -1218,8 +1196,7 @@ fn opaque_wrap_function() {
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
A a := a
|
A a := a
|
||||||
List.map [1u8, 2u8, 3u8] @A
|
List.map [1u8, 2u8, 3u8] @A"#
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
"[@A 1, @A 2, @A 3] : List (A U8)",
|
"[@A 1, @A 2, @A 3] : List (A U8)",
|
||||||
);
|
);
|
||||||
|
@ -1230,8 +1207,7 @@ fn dict_get_single() {
|
||||||
expect_success(
|
expect_success(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
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]"#,
|
r#"Ok { a: 1, c: 2 } : Result { a : Num *, c : Num * } [KeyNotFound]"#,
|
||||||
)
|
)
|
||||||
|
@ -1242,8 +1218,7 @@ fn record_of_poly_function() {
|
||||||
expect_success(
|
expect_success(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
{ a: \_ -> "a" }
|
{ a: \_ -> "a" }"#
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
r#"{ a: <function> } : { a : * -> Str }"#,
|
r#"{ a: <function> } : { a : * -> Str }"#,
|
||||||
);
|
);
|
||||||
|
@ -1254,8 +1229,7 @@ fn record_of_poly_function_and_string() {
|
||||||
expect_success(
|
expect_success(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
{ a: \_ -> "a", b: "b" }
|
{ a: \_ -> "a", b: "b" }"#
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
r#"{ a: <function>, b: "b" } : { a : * -> Str, b : Str }"#,
|
r#"{ a: <function>, b: "b" } : { a : * -> Str, b : Str }"#,
|
||||||
);
|
);
|
||||||
|
@ -1266,8 +1240,7 @@ fn newtype_by_void_is_wrapped() {
|
||||||
expect_success(
|
expect_success(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
Result.try (Err 42) (\x -> Err (x+1))
|
Result.try (Err 42) (\x -> Err (x+1))"#
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
r#"Err 42 : Result b (Num *)"#,
|
r#"Err 42 : Result b (Num *)"#,
|
||||||
);
|
);
|
||||||
|
@ -1275,8 +1248,7 @@ fn newtype_by_void_is_wrapped() {
|
||||||
expect_success(
|
expect_success(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
Result.try (Ok 42) (\x -> Ok (x+1))
|
Result.try (Ok 42) (\x -> Ok (x+1))"#
|
||||||
"#
|
|
||||||
),
|
),
|
||||||
r#"Ok 43 : Result (Num *) err"#,
|
r#"Ok 43 : Result (Num *) err"#,
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,7 +7,7 @@ use roc_load::MonomorphizedModule;
|
||||||
use roc_parse::ast::Expr;
|
use roc_parse::ast::Expr;
|
||||||
use roc_repl_eval::{
|
use roc_repl_eval::{
|
||||||
eval::jit_to_ast,
|
eval::jit_to_ast,
|
||||||
gen::{compile_to_mono, format_answer, ReplOutput},
|
gen::{compile_to_mono, format_answer},
|
||||||
ReplApp, ReplAppMemory,
|
ReplApp, ReplAppMemory,
|
||||||
};
|
};
|
||||||
use roc_reporting::report::DEFAULT_PALETTE_HTML;
|
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
|
// Compile the app
|
||||||
let target_info = TargetInfo::default_wasm32();
|
let target_info = TargetInfo::default_wasm32();
|
||||||
let mono = match compile_to_mono(arena, &src, target_info, DEFAULT_PALETTE_HTML) {
|
// TODO use this to filter out problems and warnings in wrapped defs.
|
||||||
Ok(m) => m,
|
// See the variable by the same name in the CLI REPL for how to do this!
|
||||||
Err(messages) => return Err(messages.join("\n\n")),
|
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 {
|
let MonomorphizedModule {
|
||||||
|
@ -270,10 +292,11 @@ pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
|
||||||
target_info,
|
target_info,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let var_name = String::new(); // TODO turn this into something like " # val1"
|
||||||
|
|
||||||
// Transform the Expr to a string
|
// Transform the Expr to a string
|
||||||
// `Result::Err` becomes a JS exception that will be caught and displayed
|
// `Result::Err` becomes a JS exception that will be caught and displayed
|
||||||
match format_answer(arena, res_answer, expr_type_str) {
|
let expr = format_answer(arena, res_answer);
|
||||||
ReplOutput::NoProblems { expr, expr_type } => Ok(format!("{} : {}", expr, expr_type)),
|
|
||||||
ReplOutput::Problems(lines) => Err(format!("\n{}\n", lines.join("\n\n"))),
|
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() {
|
fn print_backtrace() {
|
||||||
eprintln!("Here is the call stack that led to the crash:\n");
|
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]
|
#[no_mangle]
|
||||||
pub extern "C" fn roc_fx_stdinLine() -> RocStr {
|
pub extern "C" fn roc_fx_stdinLine() -> RocStr {
|
||||||
use std::io::{self, BufRead};
|
use std::io::BufRead;
|
||||||
|
|
||||||
let stdin = io::stdin();
|
let stdin = io::stdin();
|
||||||
let line1 = stdin.lock().lines().next().unwrap().unwrap();
|
let line1 = stdin.lock().lines().next().unwrap().unwrap();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const str = @import("str");
|
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
const str = @import("str");
|
||||||
const RocStr = str.RocStr;
|
const RocStr = str.RocStr;
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const expectEqual = testing.expectEqual;
|
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);
|
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 {};
|
const Unit = extern struct {};
|
||||||
|
|
||||||
pub export fn main() u8 {
|
pub export fn main() u8 {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use core::alloc::Layout;
|
use core::alloc::Layout;
|
||||||
use core::ffi::c_void;
|
use core::ffi::c_void;
|
||||||
use core::mem::{ManuallyDrop, MaybeUninit};
|
use core::mem::MaybeUninit;
|
||||||
use libc;
|
use libc;
|
||||||
use roc_std::{RocList, RocStr};
|
use roc_std::{RocList, RocStr};
|
||||||
use std::env;
|
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)
|
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]
|
#[no_mangle]
|
||||||
pub extern "C" fn rust_main() -> i32 {
|
pub extern "C" fn rust_main() -> i32 {
|
||||||
let arg = env::args()
|
let arg = env::args()
|
||||||
|
@ -122,9 +157,7 @@ unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 {
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn roc_fx_getLine() -> RocStr {
|
pub extern "C" fn roc_fx_getLine() -> RocStr {
|
||||||
use std::io::{self, BufRead};
|
let stdin = std::io::stdin();
|
||||||
|
|
||||||
let stdin = io::stdin();
|
|
||||||
let line1 = stdin.lock().lines().next().unwrap().unwrap();
|
let line1 = stdin.lock().lines().next().unwrap().unwrap();
|
||||||
|
|
||||||
RocStr::from(line1.as_str())
|
RocStr::from(line1.as_str())
|
||||||
|
@ -132,11 +165,10 @@ pub extern "C" fn roc_fx_getLine() -> RocStr {
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn roc_fx_getChar() -> u8 {
|
pub extern "C" fn roc_fx_getChar() -> u8 {
|
||||||
use std::io::{self, BufRead};
|
|
||||||
let mut buffer = [0];
|
let mut buffer = [0];
|
||||||
|
|
||||||
if let Err(ioerr) = io::stdin().lock().read_exact(&mut buffer[..]) {
|
if let Err(ioerr) = std::io::stdin().lock().read_exact(&mut buffer[..]) {
|
||||||
if ioerr.kind() == io::ErrorKind::UnexpectedEof {
|
if ioerr.kind() == std::io::ErrorKind::UnexpectedEof {
|
||||||
u8::MAX
|
u8::MAX
|
||||||
} else {
|
} else {
|
||||||
panic!("Got an unexpected error while reading char from stdin");
|
panic!("Got an unexpected error while reading char from stdin");
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const str = @import("str");
|
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
const str = @import("str");
|
||||||
const RocStr = str.RocStr;
|
const RocStr = str.RocStr;
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const expectEqual = testing.expectEqual;
|
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);
|
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 {};
|
const Unit = extern struct {};
|
||||||
|
|
||||||
pub export fn main() callconv(.C) u8 {
|
pub export fn main() callconv(.C) u8 {
|
||||||
|
|
|
@ -140,14 +140,14 @@ string : Str -> Parser RawStr Str
|
||||||
string = \expectedString ->
|
string = \expectedString ->
|
||||||
strToRaw expectedString
|
strToRaw expectedString
|
||||||
|> stringRaw
|
|> stringRaw
|
||||||
|> map (\_val -> expectedString)
|
|> map \_val -> expectedString
|
||||||
|
|
||||||
scalar : U32 -> Parser RawStr U32
|
scalar : U32 -> Parser RawStr U32
|
||||||
scalar = \expectedScalar ->
|
scalar = \expectedScalar ->
|
||||||
expectedScalar
|
expectedScalar
|
||||||
|> strFromScalar
|
|> strFromScalar
|
||||||
|> string
|
|> string
|
||||||
|> map (\_ -> expectedScalar)
|
|> map \_ -> expectedScalar
|
||||||
|
|
||||||
# Matches any codeunit
|
# Matches any codeunit
|
||||||
anyCodeunit : Parser RawStr U8
|
anyCodeunit : Parser RawStr U8
|
||||||
|
@ -181,7 +181,7 @@ digit =
|
||||||
|> List.map \digitNum ->
|
|> List.map \digitNum ->
|
||||||
digitNum
|
digitNum
|
||||||
|> codeunit
|
|> codeunit
|
||||||
|> map (\_ -> digitNum)
|
|> map \_ -> digitNum
|
||||||
|
|
||||||
oneOf digitParsers
|
oneOf digitParsers
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ digits =
|
||||||
|> map \digitsList ->
|
|> map \digitsList ->
|
||||||
digitsList
|
digitsList
|
||||||
|> List.map Num.intCast
|
|> 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.
|
## 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);
|
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 {
|
struct RocStr {
|
||||||
char *bytes;
|
char *bytes;
|
||||||
size_t len;
|
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); }
|
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 {
|
struct RocStr {
|
||||||
char* bytes;
|
char* bytes;
|
||||||
size_t len;
|
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)
|
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]
|
#[no_mangle]
|
||||||
pub extern "C" fn rust_main() -> i32 {
|
pub extern "C" fn rust_main() -> i32 {
|
||||||
let mut roc_str = RocStr::default();
|
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);
|
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 mem = std.mem;
|
||||||
const Allocator = mem.Allocator;
|
const Allocator = mem.Allocator;
|
||||||
|
|
||||||
|
@ -103,4 +131,4 @@ pub fn main() u8 {
|
||||||
stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable;
|
stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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); }
|
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 {
|
struct RocStr {
|
||||||
char* bytes;
|
char* bytes;
|
||||||
size_t len;
|
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)
|
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]
|
#[no_mangle]
|
||||||
pub extern "C" fn rust_main() -> i32 {
|
pub extern "C" fn rust_main() -> i32 {
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue