merge upstream/main

This commit is contained in:
Luke Boswell 2022-11-06 09:27:46 +11:00
commit cec67721e6
No known key found for this signature in database
GPG key ID: 0E908525B2C7BD68
59 changed files with 2542 additions and 990 deletions

View file

@ -18,6 +18,7 @@ jobs:
with:
use-quiet-mode: 'yes'
use-verbose-mode: 'yes'
check-modified-files-only: 'yes'
base-branch: 'main'
check-modified-files-only: 'yes'
if: ${{ github.event_name == 'pull_request' }}

8
Cargo.lock generated
View file

@ -3234,6 +3234,7 @@ version = "0.0.1"
dependencies = [
"indoc",
"lazy_static",
"roc_build",
"roc_cli",
"roc_repl_cli",
"roc_test_utils",
@ -3412,6 +3413,7 @@ dependencies = [
"roc_fmt",
"roc_gen_llvm",
"roc_glue",
"roc_intern",
"roc_linker",
"roc_load",
"roc_module",
@ -3921,6 +3923,7 @@ dependencies = [
"rustyline",
"rustyline-derive",
"target-lexicon",
"unicode-segmentation",
]
[[package]]
@ -3958,6 +3961,7 @@ dependencies = [
"roc_builtins",
"roc_can",
"roc_collections",
"roc_error_macros",
"roc_gen_llvm",
"roc_intern",
"roc_load",
@ -5165,9 +5169,9 @@ checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
[[package]]
name = "unicode-segmentation"
version = "1.9.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
[[package]]
name = "unicode-width"

View file

@ -60,6 +60,7 @@ roc_editor = { path = "../editor", optional = true }
roc_linker = { path = "../linker" }
roc_repl_cli = { path = "../repl_cli", optional = true }
roc_tracing = { path = "../tracing" }
roc_intern = { path = "../compiler/intern" }
clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions"] }
const_format = { version = "0.2.23", features = ["const_generics"] }
bumpalo = { version = "3.11.0", features = ["collections"] }

View file

@ -1,15 +1,13 @@
use bumpalo::Bump;
use roc_build::{
link::{link, preprocess_host_wasm32, rebuild_host, LinkType, LinkingStrategy},
program::{self, Problems},
program::{self, CodeGenOptions, Problems},
};
use roc_builtins::bitcode;
use roc_collections::VecMap;
use roc_load::{
EntryPoint, ExecutionMode, Expectations, LoadConfig, LoadMonomorphizedError, LoadedModule,
EntryPoint, ExecutionMode, ExpectMetadata, LoadConfig, LoadMonomorphizedError, LoadedModule,
LoadingProblem, Threading,
};
use roc_module::symbol::{Interns, ModuleId};
use roc_mono::ir::OptLevel;
use roc_reporting::report::RenderTarget;
use roc_target::TargetInfo;
@ -30,12 +28,11 @@ fn report_timing(buf: &mut String, label: &str, duration: Duration) {
.unwrap()
}
pub struct BuiltFile {
pub struct BuiltFile<'a> {
pub binary_path: PathBuf,
pub problems: Problems,
pub total_time: Duration,
pub expectations: VecMap<ModuleId, Expectations>,
pub interns: Interns,
pub expect_metadata: ExpectMetadata<'a>,
}
pub enum BuildOrdering {
@ -60,8 +57,7 @@ pub fn build_file<'a>(
arena: &'a Bump,
target: &Triple,
app_module_path: PathBuf,
opt_level: OptLevel,
emit_debug_info: bool,
code_gen_options: CodeGenOptions,
emit_timings: bool,
link_type: LinkType,
linking_strategy: LinkingStrategy,
@ -69,7 +65,7 @@ pub fn build_file<'a>(
threading: Threading,
wasm_dev_stack_bytes: Option<u32>,
order: BuildOrdering,
) -> Result<BuiltFile, BuildFileError<'a>> {
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let compilation_start = Instant::now();
let target_info = TargetInfo::from(target);
@ -121,7 +117,7 @@ pub fn build_file<'a>(
match roc_target::OperatingSystem::from(target.operating_system) {
Wasi => {
if matches!(opt_level, OptLevel::Development) {
if matches!(code_gen_options.opt_level, OptLevel::Development) {
("wasm", "wasm", Some("wasm"))
} else {
("zig", "bc", Some("wasm"))
@ -180,7 +176,7 @@ pub fn build_file<'a>(
};
let rebuild_thread = spawn_rebuild_thread(
opt_level,
code_gen_options.opt_level,
linking_strategy,
prebuilt,
host_input_path.clone(),
@ -241,11 +237,8 @@ pub fn build_file<'a>(
// inside a nested scope without causing a borrow error!
let mut loaded = loaded;
let problems = program::report_problems_monomorphized(&mut loaded);
let expectations = std::mem::take(&mut loaded.expectations);
let loaded = loaded;
let interns = loaded.interns.clone();
enum HostRebuildTiming {
BeforeApp(u128),
ConcurrentWithApp(JoinHandle<u128>),
@ -266,13 +259,12 @@ pub fn build_file<'a>(
HostRebuildTiming::ConcurrentWithApp(rebuild_thread)
};
let (roc_app_bytes, code_gen_timing) = program::gen_from_mono_module(
let (roc_app_bytes, code_gen_timing, expect_metadata) = program::gen_from_mono_module(
arena,
loaded,
&app_module_path,
target,
opt_level,
emit_debug_info,
code_gen_options,
&preprocessed_host_path,
wasm_dev_stack_bytes,
);
@ -351,7 +343,7 @@ pub fn build_file<'a>(
let str_host_obj_path = bitcode::get_builtins_host_obj_path();
if matches!(opt_level, OptLevel::Development) {
if matches!(code_gen_options.backend, program::CodeGenBackend::Assembly) {
inputs.push(&str_host_obj_path);
}
@ -388,8 +380,7 @@ pub fn build_file<'a>(
binary_path,
problems,
total_time,
interns,
expectations,
expect_metadata,
})
}

View file

@ -7,11 +7,9 @@ use build::BuiltFile;
use bumpalo::Bump;
use clap::{Arg, ArgMatches, Command, ValueSource};
use roc_build::link::{LinkType, LinkingStrategy};
use roc_build::program::Problems;
use roc_collections::VecMap;
use roc_build::program::{CodeGenBackend, CodeGenOptions, Problems};
use roc_error_macros::{internal_error, user_error};
use roc_load::{Expectations, LoadingProblem, Threading};
use roc_module::symbol::{Interns, ModuleId};
use roc_load::{ExpectMetadata, LoadingProblem, Threading};
use roc_mono::ir::OptLevel;
use std::env;
use std::ffi::{CString, OsStr};
@ -432,7 +430,7 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
let mut writer = std::io::stdout();
let (failed, passed) = roc_repl_expect::run::run_expects(
let (failed, passed) = roc_repl_expect::run::run_toplevel_expects(
&mut writer,
roc_reporting::report::RenderTarget::ColorTerminal,
arena,
@ -481,18 +479,33 @@ pub fn build(
use build::build_file;
use BuildConfig::*;
let arena = Bump::new();
let filename = matches.value_of_os(ROC_FILE).unwrap();
let opt_level = match (
// the process will end after this function,
// so we don't want to spend time freeing these values
let arena = ManuallyDrop::new(Bump::new());
let code_gen_backend = if matches!(triple.architecture, Architecture::Wasm32) {
CodeGenBackend::Wasm
} else {
match matches.is_present(FLAG_DEV) {
true => CodeGenBackend::Assembly,
false => CodeGenBackend::Llvm,
}
};
let opt_level = if let BuildConfig::BuildAndRunIfNoErrors = config {
OptLevel::Development
} else {
match (
matches.is_present(FLAG_OPTIMIZE),
matches.is_present(FLAG_OPT_SIZE),
matches.is_present(FLAG_DEV),
) {
(true, false, false) => OptLevel::Optimize,
(false, true, false) => OptLevel::Size,
(false, false, true) => OptLevel::Development,
(false, false, false) => OptLevel::Normal,
_ => user_error!("build can be only one of `--dev`, `--optimize`, or `--opt-size`"),
(true, false) => OptLevel::Optimize,
(false, true) => OptLevel::Size,
(false, false) => OptLevel::Normal,
(true, true) => {
user_error!("build can be only one of `--optimize` and `--opt-size`")
}
}
};
let emit_debug_info = matches.is_present(FLAG_DEBUG);
let emit_timings = matches.is_present(FLAG_TIME);
@ -508,7 +521,7 @@ pub fn build(
};
let wasm_dev_backend = matches!(opt_level, OptLevel::Development)
&& matches!(triple.architecture, Architecture::Wasm32);
&& matches!(code_gen_backend, CodeGenBackend::Wasm);
let linking_strategy = if wasm_dev_backend {
LinkingStrategy::Additive
@ -528,6 +541,8 @@ pub fn build(
// We make an exception for Wasm, because cross-compiling is the norm in that case.
triple != Triple::host() && !matches!(triple.architecture, Architecture::Wasm32)
};
let filename = matches.value_of_os(ROC_FILE).unwrap();
let path = Path::new(filename);
// Spawn the root task
@ -559,12 +574,18 @@ pub fn build(
BuildAndRunIfNoErrors => BuildOrdering::BuildIfChecks,
_ => BuildOrdering::AlwaysBuild,
};
let code_gen_options = CodeGenOptions {
backend: code_gen_backend,
opt_level,
emit_debug_info,
};
let res_binary_path = build_file(
&arena,
&triple,
path.to_path_buf(),
opt_level,
emit_debug_info,
code_gen_options,
emit_timings,
link_type,
linking_strategy,
@ -579,8 +600,7 @@ pub fn build(
binary_path,
problems,
total_time,
expectations,
interns,
expect_metadata,
}) => {
match config {
BuildOnly => {
@ -593,7 +613,7 @@ pub fn build(
// No need to waste time freeing this memory,
// since the process is about to exit anyway.
std::mem::forget(arena);
// std::mem::forget(arena);
print_problems(problems, total_time);
println!(" while successfully building:\n\n {generated_filename}");
@ -616,7 +636,7 @@ pub fn build(
// ManuallyDrop will leak the bytes because we don't drop manually
let bytes = &ManuallyDrop::new(std::fs::read(&binary_path).unwrap());
roc_run(arena, opt_level, triple, args, bytes, expectations, interns)
roc_run(&arena, opt_level, triple, args, bytes, expect_metadata)
}
BuildAndRunIfNoErrors => {
debug_assert!(
@ -637,7 +657,7 @@ pub fn build(
// ManuallyDrop will leak the bytes because we don't drop manually
let bytes = &ManuallyDrop::new(std::fs::read(&binary_path).unwrap());
roc_run(arena, opt_level, triple, args, bytes, expectations, interns)
roc_run(&arena, opt_level, triple, args, bytes, expect_metadata)
}
}
}
@ -703,13 +723,12 @@ fn print_problems(problems: Problems, total_time: std::time::Duration) {
}
fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
arena: &Bump,
opt_level: OptLevel,
triple: Triple,
args: I,
binary_bytes: &[u8],
expectations: VecMap<ModuleId, Expectations>,
interns: Interns,
expect_metadata: ExpectMetadata,
) -> io::Result<i32> {
match triple.architecture {
Architecture::Wasm32 => {
@ -720,10 +739,6 @@ fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
.strip_prefix(env::current_dir().unwrap())
.unwrap_or(path);
// No need to waste time freeing this memory,
// since the process is about to exit anyway.
std::mem::forget(arena);
#[cfg(target_family = "unix")]
{
use std::os::unix::ffi::OsStrExt;
@ -748,7 +763,7 @@ fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
Ok(0)
}
_ => roc_run_native(arena, opt_level, args, binary_bytes, expectations, interns),
_ => roc_run_native(arena, opt_level, args, binary_bytes, expect_metadata),
}
}
@ -848,35 +863,32 @@ fn make_argv_envp_windows<'a, I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
/// Run on the native OS (not on wasm)
#[cfg(target_family = "unix")]
fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
arena: Bump,
arena: &Bump,
opt_level: OptLevel,
args: I,
binary_bytes: &[u8],
expectations: VecMap<ModuleId, Expectations>,
interns: Interns,
expect_metadata: ExpectMetadata,
) -> std::io::Result<i32> {
use bumpalo::collections::CollectIn;
unsafe {
let executable = roc_run_executable_file_path(binary_bytes)?;
let (argv_cstrings, envp_cstrings) = make_argv_envp(&arena, &executable, args);
let (argv_cstrings, envp_cstrings) = make_argv_envp(arena, &executable, args);
let argv: bumpalo::collections::Vec<*const c_char> = argv_cstrings
.iter()
.map(|s| s.as_ptr())
.chain([std::ptr::null()])
.collect_in(&arena);
.collect_in(arena);
let envp: bumpalo::collections::Vec<*const c_char> = envp_cstrings
.iter()
.map(|s| s.as_ptr())
.chain([std::ptr::null()])
.collect_in(&arena);
.collect_in(arena);
match opt_level {
OptLevel::Development => {
roc_run_native_debug(executable, &argv, &envp, expectations, interns)
}
OptLevel::Development => roc_dev_native(arena, executable, argv, envp, expect_metadata),
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => {
roc_run_native_fast(executable, &argv, &envp);
}
@ -950,14 +962,76 @@ impl ExecutableFile {
// with Expect
#[cfg(target_family = "unix")]
unsafe fn roc_run_native_debug(
_executable: ExecutableFile,
_argv: &[*const c_char],
_envp: &[*const c_char],
_expectations: VecMap<ModuleId, Expectations>,
_interns: Interns,
) {
todo!()
fn roc_dev_native(
arena: &Bump,
executable: ExecutableFile,
argv: bumpalo::collections::Vec<*const c_char>,
envp: bumpalo::collections::Vec<*const c_char>,
expect_metadata: ExpectMetadata,
) -> ! {
use roc_repl_expect::run::ExpectMemory;
use signal_hook::{consts::signal::SIGCHLD, consts::signal::SIGUSR1, iterator::Signals};
let ExpectMetadata {
mut expectations,
interns,
layout_interner,
} = expect_metadata;
let mut signals = Signals::new(&[SIGCHLD, SIGUSR1]).unwrap();
// let shm_name =
let shm_name = format!("/roc_expect_buffer_{}", std::process::id());
let memory = ExpectMemory::create_or_reuse_mmap(&shm_name);
let layout_interner = layout_interner.into_global();
let mut writer = std::io::stdout();
match unsafe { libc::fork() } {
0 => unsafe {
// we are the child
executable.execve(&argv, &envp);
// Display a human-friendly error message
println!("Error {:?}", std::io::Error::last_os_error());
std::process::exit(1);
},
-1 => {
// something failed
// Display a human-friendly error message
println!("Error {:?}", std::io::Error::last_os_error());
std::process::exit(1)
}
1.. => {
for sig in &mut signals {
match sig {
SIGCHLD => break,
SIGUSR1 => {
// this is the signal we use for an expect failure. Let's see what the child told us
roc_repl_expect::run::render_expects_in_memory(
&mut writer,
arena,
&mut expectations,
&interns,
&layout_interner,
&memory,
)
.unwrap();
}
_ => println!("received signal {}", sig),
}
}
std::process::exit(0)
}
_ => unreachable!(),
}
}
#[cfg(target_os = "linux")]
@ -1036,12 +1110,11 @@ fn roc_run_executable_file_path(binary_bytes: &[u8]) -> std::io::Result<Executab
/// Run on the native OS (not on wasm)
#[cfg(not(target_family = "unix"))]
fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
arena: &Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
opt_level: OptLevel,
args: I,
binary_bytes: &[u8],
_expectations: VecMap<ModuleId, Expectations>,
_interns: Interns,
_expect_metadata: ExpectMetadata,
) -> io::Result<i32> {
use bumpalo::collections::CollectIn;
@ -1055,18 +1128,18 @@ fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
.iter()
.map(|s| s.as_ptr())
.chain([std::ptr::null()])
.collect_in(&arena);
.collect_in(arena);
let envp: bumpalo::collections::Vec<*const c_char> = envp_cstrings
.iter()
.map(|s| s.as_ptr())
.chain([std::ptr::null()])
.collect_in(&arena);
.collect_in(arena);
match opt_level {
OptLevel::Development => {
// roc_run_native_debug(executable, &argv, &envp, expectations, interns)
todo!()
internal_error!("running `expect`s does not currently work on windows")
}
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => {
roc_run_native_fast(executable, &argv, &envp);

View file

@ -178,14 +178,7 @@ fn main() -> io::Result<()> {
}
}
}
Some((CMD_REPL, _)) => {
{
roc_repl_cli::main()?;
// Exit 0 if the repl exited normally
Ok(0)
}
}
Some((CMD_REPL, _)) => Ok(roc_repl_cli::main()),
Some((CMD_EDIT, matches)) => {
match matches
.values_of_os(DIRECTORY_OR_FILES)

View file

@ -65,6 +65,34 @@ export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void {
std.process.exit(0);
}
extern fn kill(pid: c_int, sig: c_int) c_int;
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
extern fn getppid() c_int;
fn roc_getppid() callconv(.C) c_int {
return getppid();
}
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
return kill(pid, sig);
}
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
return shm_open(name, oflag, mode);
}
fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
return mmap(addr, length, prot, flags, fd, offset);
}
comptime {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
}
}
const Unit = extern struct {};
pub export fn main() i32 {

View file

@ -64,6 +64,34 @@ export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void {
std.process.exit(0);
}
extern fn kill(pid: c_int, sig: c_int) c_int;
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
extern fn getppid() c_int;
fn roc_getppid() callconv(.C) c_int {
return getppid();
}
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
return kill(pid, sig);
}
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
return shm_open(name, oflag, mode);
}
fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
return mmap(addr, length, prot, flags, fd, offset);
}
comptime {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
}
}
const Unit = extern struct {};
pub export fn main() i32 {

View file

@ -1,4 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
@ -13,7 +14,6 @@ comptime {
// -fcompiler-rt in link.rs instead of doing this. Note that this
// workaround is present in many host.zig files, so make sure to undo
// it everywhere!
const builtin = @import("builtin");
if (builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
@ -81,6 +81,34 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size);
}
extern fn kill(pid: c_int, sig: c_int) c_int;
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
extern fn getppid() c_int;
fn roc_getppid() callconv(.C) c_int {
return getppid();
}
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
return kill(pid, sig);
}
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
return shm_open(name, oflag, mode);
}
fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
return mmap(addr, length, prot, flags, fd, offset);
}
comptime {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
}
}
pub export fn main() u8 {
const stdout = std.io.getStdOut().writer();

View file

@ -80,6 +80,34 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size);
}
extern fn kill(pid: c_int, sig: c_int) c_int;
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
extern fn getppid() c_int;
fn roc_getppid() callconv(.C) c_int {
return getppid();
}
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
return kill(pid, sig);
}
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
return shm_open(name, oflag, mode);
}
fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
return mmap(addr, length, prot, flags, fd, offset);
}
comptime {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
}
}
// warning! the array is currently stack-allocated so don't make this too big
const NUM_NUMS = 100;

View file

@ -1,6 +1,6 @@
const std = @import("std");
const str = @import("str");
const builtin = @import("builtin");
const str = @import("str");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
@ -85,9 +85,39 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size);
}
extern fn kill(pid: c_int, sig: c_int) c_int;
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
extern fn getppid() c_int;
fn roc_getppid() callconv(.C) c_int {
return getppid();
}
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
return kill(pid, sig);
}
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
return shm_open(name, oflag, mode);
}
fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
return mmap(addr, length, prot, flags, fd, offset);
}
comptime {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
}
}
const Unit = extern struct {};
pub export fn main() callconv(.C) u8 {
pub fn main() !u8 {
const stderr = std.io.getStdErr().writer();
// The size might be zero; if so, make it at least 8 so that we don't have a nullptr
const size = std.math.max(@intCast(usize, roc__mainForHost_size()), 8);
const raw_output = roc_alloc(@intCast(usize, size), @alignOf(u64)).?;
@ -108,7 +138,6 @@ pub export fn main() callconv(.C) u8 {
const nanos = timer.read();
const seconds = (@intToFloat(f64, nanos) / 1_000_000_000.0);
const stderr = std.io.getStdErr().writer();
stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable;
return 0;

View file

@ -3,7 +3,7 @@ use roc_error_macros::internal_error;
pub use roc_gen_llvm::llvm::build::FunctionIterator;
use roc_gen_llvm::llvm::build::{module_from_builtins, LlvmBackendMode};
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
use roc_load::{EntryPoint, LoadedModule, MonomorphizedModule};
use roc_load::{EntryPoint, ExpectMetadata, LoadedModule, MonomorphizedModule};
use roc_module::symbol::{Interns, ModuleId};
use roc_mono::ir::OptLevel;
use roc_region::all::LineInfo;
@ -174,47 +174,60 @@ impl Deref for CodeObject {
}
}
#[derive(Debug, Clone, Copy)]
pub enum CodeGenBackend {
Assembly,
Llvm,
Wasm,
}
#[derive(Debug, Clone, Copy)]
pub struct CodeGenOptions {
pub backend: CodeGenBackend,
pub opt_level: OptLevel,
pub emit_debug_info: bool,
}
type GenFromMono<'a> = (CodeObject, CodeGenTiming, ExpectMetadata<'a>);
#[allow(clippy::too_many_arguments)]
pub fn gen_from_mono_module(
arena: &bumpalo::Bump,
loaded: MonomorphizedModule,
pub fn gen_from_mono_module<'a>(
arena: &'a bumpalo::Bump,
loaded: MonomorphizedModule<'a>,
roc_file_path: &Path,
target: &target_lexicon::Triple,
opt_level: OptLevel,
emit_debug_info: bool,
code_gen_options: CodeGenOptions,
preprocessed_host_path: &Path,
wasm_dev_stack_bytes: Option<u32>,
) -> (CodeObject, CodeGenTiming) {
match opt_level {
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => gen_from_mono_module_llvm(
arena,
loaded,
roc_file_path,
target,
opt_level,
emit_debug_info,
),
OptLevel::Development => gen_from_mono_module_dev(
) -> GenFromMono<'a> {
match code_gen_options.backend {
CodeGenBackend::Assembly => gen_from_mono_module_dev(
arena,
loaded,
target,
preprocessed_host_path,
wasm_dev_stack_bytes,
),
CodeGenBackend::Llvm => {
gen_from_mono_module_llvm(arena, loaded, roc_file_path, target, code_gen_options)
}
CodeGenBackend::Wasm => {
// emit wasm via the llvm backend
gen_from_mono_module_llvm(arena, loaded, roc_file_path, target, code_gen_options)
}
}
}
// TODO how should imported modules factor into this? What if those use builtins too?
// TODO this should probably use more helper functions
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
fn gen_from_mono_module_llvm(
arena: &bumpalo::Bump,
loaded: MonomorphizedModule,
fn gen_from_mono_module_llvm<'a>(
arena: &'a bumpalo::Bump,
loaded: MonomorphizedModule<'a>,
roc_file_path: &Path,
target: &target_lexicon::Triple,
opt_level: OptLevel,
emit_debug_info: bool,
) -> (CodeObject, CodeGenTiming) {
code_gen_options: CodeGenOptions,
) -> GenFromMono<'a> {
use crate::target::{self, convert_opt_level};
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::context::Context;
@ -263,6 +276,12 @@ fn gen_from_mono_module_llvm(
}
}
let CodeGenOptions {
backend: _,
opt_level,
emit_debug_info,
} = code_gen_options;
let builder = context.create_builder();
let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module);
let (mpm, _fpm) = roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level);
@ -278,7 +297,11 @@ fn gen_from_mono_module_llvm(
interns: loaded.interns,
module,
target_info,
mode: LlvmBackendMode::Binary,
mode: match opt_level {
OptLevel::Development => LlvmBackendMode::BinaryDev,
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => LlvmBackendMode::Binary,
},
exposed_to_host: loaded.exposed_to_host.values.keys().copied().collect(),
};
@ -430,17 +453,22 @@ fn gen_from_mono_module_llvm(
(
CodeObject::MemoryBuffer(memory_buffer),
CodeGenTiming { code_gen },
ExpectMetadata {
interns: env.interns,
layout_interner: loaded.layout_interner,
expectations: loaded.expectations,
},
)
}
#[cfg(feature = "target-wasm32")]
fn gen_from_mono_module_dev(
arena: &bumpalo::Bump,
loaded: MonomorphizedModule,
fn gen_from_mono_module_dev<'a>(
arena: &'a bumpalo::Bump,
loaded: MonomorphizedModule<'a>,
target: &target_lexicon::Triple,
preprocessed_host_path: &Path,
wasm_dev_stack_bytes: Option<u32>,
) -> (CodeObject, CodeGenTiming) {
) -> GenFromMono<'a> {
use target_lexicon::Architecture;
match target.architecture {
@ -458,13 +486,13 @@ fn gen_from_mono_module_dev(
}
#[cfg(not(feature = "target-wasm32"))]
pub fn gen_from_mono_module_dev(
arena: &bumpalo::Bump,
loaded: MonomorphizedModule,
pub fn gen_from_mono_module_dev<'a>(
arena: &'a bumpalo::Bump,
loaded: MonomorphizedModule<'a>,
target: &target_lexicon::Triple,
_host_input_path: &Path,
_wasm_dev_stack_bytes: Option<u32>,
) -> (CodeObject, CodeGenTiming) {
) -> GenFromMono<'a> {
use target_lexicon::Architecture;
match target.architecture {
@ -476,12 +504,12 @@ pub fn gen_from_mono_module_dev(
}
#[cfg(feature = "target-wasm32")]
fn gen_from_mono_module_dev_wasm32(
arena: &bumpalo::Bump,
loaded: MonomorphizedModule,
fn gen_from_mono_module_dev_wasm32<'a>(
arena: &'a bumpalo::Bump,
loaded: MonomorphizedModule<'a>,
preprocessed_host_path: &Path,
wasm_dev_stack_bytes: Option<u32>,
) -> (CodeObject, CodeGenTiming) {
) -> GenFromMono<'a> {
let code_gen_start = Instant::now();
let MonomorphizedModule {
module_id,
@ -530,14 +558,19 @@ fn gen_from_mono_module_dev_wasm32(
(
CodeObject::Vector(final_binary_bytes),
CodeGenTiming { code_gen },
ExpectMetadata {
interns,
layout_interner,
expectations: loaded.expectations,
},
)
}
fn gen_from_mono_module_dev_assembly(
arena: &bumpalo::Bump,
loaded: MonomorphizedModule,
fn gen_from_mono_module_dev_assembly<'a>(
arena: &'a bumpalo::Bump,
loaded: MonomorphizedModule<'a>,
target: &target_lexicon::Triple,
) -> (CodeObject, CodeGenTiming) {
) -> GenFromMono<'a> {
let code_gen_start = Instant::now();
let lazy_literals = true;
@ -569,5 +602,13 @@ fn gen_from_mono_module_dev_assembly(
.write()
.expect("failed to build output object");
(CodeObject::Vector(module_out), CodeGenTiming { code_gen })
(
CodeObject::Vector(module_out),
CodeGenTiming { code_gen },
ExpectMetadata {
interns,
layout_interner,
expectations: loaded.expectations,
},
)
}

View file

@ -1,4 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
const SIGUSR1: c_int = 10;
@ -18,6 +19,71 @@ pub fn setSharedBuffer(ptr: [*]u8, length: usize) callconv(.C) usize {
return 0;
}
pub fn expectFailedStart() callconv(.C) [*]u8 {
pub fn expectFailedStartSharedBuffer() callconv(.C) [*]u8 {
return SHARED_BUFFER.ptr;
}
pub fn expectFailedStartSharedFile() callconv(.C) [*]u8 {
// IMPORTANT: shared memory object names must begin with / and contain no other slashes!
var name: [100]u8 = undefined;
_ = std.fmt.bufPrint(name[0..100], "/roc_expect_buffer_{}\x00", .{roc_getppid()}) catch unreachable;
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
const shared_fd = roc_shm_open(@ptrCast(*const i8, &name), O_RDWR | O_CREAT, 0o666);
const length = 4096;
const shared_ptr = roc_mmap(
null,
length,
PROT_WRITE,
MAP_SHARED,
shared_fd,
0,
);
const ptr = @ptrCast([*]u8, shared_ptr);
return ptr;
} else {
unreachable;
}
}
extern fn roc_send_signal(pid: c_int, sig: c_int) c_int;
extern fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
extern fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
extern fn roc_getppid() c_int;
pub fn readSharedBufferEnv() callconv(.C) void {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
// IMPORTANT: shared memory object names must begin with / and contain no other slashes!
var name: [100]u8 = undefined;
_ = std.fmt.bufPrint(name[0..100], "/roc_expect_buffer_{}\x00", .{roc_getppid()}) catch unreachable;
const shared_fd = roc_shm_open(@ptrCast(*const i8, &name), O_RDWR | O_CREAT, 0o666);
const length = 4096;
const shared_ptr = roc_mmap(
null,
length,
PROT_WRITE,
MAP_SHARED,
shared_fd,
0,
);
const ptr = @ptrCast([*]u8, shared_ptr);
SHARED_BUFFER = ptr[0..length];
}
}
pub fn expectFailedFinalize() callconv(.C) void {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
const parent_pid = roc_getppid();
_ = roc_send_signal(parent_pid, SIGUSR1);
}
}

View file

@ -169,10 +169,14 @@ comptime {
@export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });
if (builtin.target.cpu.arch != .wasm32) {
exportUtilsFn(expect.expectFailedStart, "expect_failed_start");
exportUtilsFn(expect.expectFailedStartSharedBuffer, "expect_failed_start_shared_buffer");
exportUtilsFn(expect.expectFailedStartSharedFile, "expect_failed_start_shared_file");
exportUtilsFn(expect.expectFailedFinalize, "expect_failed_finalize");
// sets the buffer used for expect failures
@export(expect.setSharedBuffer, .{ .name = "set_shared_buffer", .linkage = .Weak });
exportUtilsFn(expect.readSharedBufferEnv, "read_env_shared_buffer");
}
if (builtin.target.cpu.arch == .aarch64) {

View file

@ -22,6 +22,24 @@ extern fn roc_panic(c_ptr: *const anyopaque, tag_id: u32) callconv(.C) void;
// should work just like libc memcpy (we can't assume libc is present)
extern fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
extern fn kill(pid: c_int, sig: c_int) c_int;
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
extern fn getppid() c_int;
fn testing_roc_getppid() callconv(.C) c_int {
return getppid();
}
fn testing_roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
return kill(pid, sig);
}
fn testing_roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
return shm_open(name, oflag, mode);
}
fn testing_roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
return mmap(addr, length, prot, flags, fd, offset);
}
comptime {
const builtin = @import("builtin");
// During tests, use the testing allocators to satisfy these functions.
@ -31,6 +49,13 @@ comptime {
@export(testing_roc_dealloc, .{ .name = "roc_dealloc", .linkage = .Strong });
@export(testing_roc_panic, .{ .name = "roc_panic", .linkage = .Strong });
@export(testing_roc_memcpy, .{ .name = "roc_memcpy", .linkage = .Strong });
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(testing_roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(testing_roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(testing_roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
@export(testing_roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
}
}
}

View file

@ -215,6 +215,7 @@ fn run_command(mut command: Command, flaky_fail_counter: usize) {
// Flaky test errors that only occur sometimes on MacOS ci server.
if error_str.contains("FileNotFound")
|| error_str.contains("unable to save cached ZIR code")
|| error_str.contains("LLVM failed to emit asm")
{
if flaky_fail_counter == 10 {
panic!("{} failed 10 times in a row. The following error is unlikely to be a flaky error: {}", command_str, error_str);

View file

@ -368,30 +368,26 @@ contains = \list, needle ->
## You can use it in a pipeline:
##
## [2, 4, 8]
## |> List.walk { start: 0, step: Num.add }
## |> List.walk 0 Num.add
##
## This returns 14 because:
## * `state` starts at 0 (because of `start: 0`)
## * `state` starts at 0
## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`.
##
## Here is a table of how `state` changes as [List.walk] walks over the elements
## `[2, 4, 8]` using #Num.add as its `step` function to determine the next `state`.
## `[2, 4, 8]` using [Num.add] as its `step` function to determine the next `state`.
##
## `state` | `elem` | `step state elem` (`Num.add state elem`)
## --------+--------+-----------------------------------------
## state | elem | Num.add state elem
## :---: | :---: | :----------------:
## 0 | |
## 0 | 2 | 2
## 2 | 4 | 6
## 6 | 8 | 14
##
## So `state` goes through these changes:
## 1. `0` (because of `start: 0`)
## 2. `1` (because of `Num.add state elem` with `state` = 0 and `elem` = 1
## The following returns -6:
##
## [1, 2, 3]
## |> List.walk { start: 0, step: Num.sub }
##
## This returns -6 because
## |> List.walk 0 Num.sub
##
## Note that in other languages, `walk` is sometimes called `reduce`,
## `fold`, `foldLeft`, or `foldl`.

View file

@ -404,8 +404,12 @@ pub const UTILS_INCREF: &str = "roc_builtins.utils.incref";
pub const UTILS_DECREF: &str = "roc_builtins.utils.decref";
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";
pub const UTILS_EXPECT_FAILED_START: &str = "roc_builtins.utils.expect_failed_start";
pub const UTILS_EXPECT_FAILED_START_SHARED_BUFFER: &str =
"roc_builtins.utils.expect_failed_start_shared_buffer";
pub const UTILS_EXPECT_FAILED_START_SHARED_FILE: &str =
"roc_builtins.utils.expect_failed_start_shared_file";
pub const UTILS_EXPECT_FAILED_FINALIZE: &str = "roc_builtins.utils.expect_failed_finalize";
pub const UTILS_EXPECT_READ_ENV_SHARED_BUFFER: &str = "roc_builtins.utils.read_env_shared_buffer";
pub const UTILS_LONGJMP: &str = "longjmp";
pub const UTILS_SETJMP: &str = "setjmp";

View file

@ -163,6 +163,7 @@ impl<'a, 'ctx> Scope<'a, 'ctx> {
pub enum LlvmBackendMode {
/// Assumes primitives (roc_alloc, roc_panic, etc) are provided by the host
Binary,
BinaryDev,
/// Creates a test wrapper around the main roc function to catch and report panics.
/// Provides a testing implementation of primitives (roc_alloc, roc_panic, etc)
GenTest,
@ -174,6 +175,7 @@ impl LlvmBackendMode {
pub(crate) fn has_host(self) -> bool {
match self {
LlvmBackendMode::Binary => true,
LlvmBackendMode::BinaryDev => true,
LlvmBackendMode::GenTest => false,
LlvmBackendMode::WasmGenTest => true,
LlvmBackendMode::CliTest => false,
@ -184,6 +186,7 @@ impl LlvmBackendMode {
fn returns_roc_result(self) -> bool {
match self {
LlvmBackendMode::Binary => false,
LlvmBackendMode::BinaryDev => false,
LlvmBackendMode::GenTest => true,
LlvmBackendMode::WasmGenTest => true,
LlvmBackendMode::CliTest => true,
@ -193,6 +196,7 @@ impl LlvmBackendMode {
fn runs_expects(self) -> bool {
match self {
LlvmBackendMode::Binary => false,
LlvmBackendMode::BinaryDev => true,
LlvmBackendMode::GenTest => false,
LlvmBackendMode::WasmGenTest => false,
LlvmBackendMode::CliTest => true,
@ -2824,6 +2828,10 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
lookups,
);
if let LlvmBackendMode::BinaryDev = env.mode {
crate::llvm::expect::finalize(env);
}
bd.build_unconditional_branch(then_block);
}
roc_target::PtrWidth::Bytes4 => {
@ -3925,7 +3933,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
)
}
LlvmBackendMode::Binary => {}
LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev => {}
}
// a generic version that writes the result into a passed *u8 pointer
@ -3976,7 +3984,9 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
roc_result_type(env, roc_function.get_type().get_return_type().unwrap()).into()
}
LlvmBackendMode::Binary => basic_type_from_layout(env, &return_layout),
LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev => {
basic_type_from_layout(env, &return_layout)
}
};
let size: BasicValueEnum = return_type.size_of().unwrap().into();
@ -4948,7 +4958,7 @@ pub fn build_proc<'a, 'ctx, 'env>(
GenTest | WasmGenTest | CliTest => {
/* no host, or exposing types is not supported */
}
Binary => {
Binary | BinaryDev => {
for (alias_name, (generated_function, top_level, layout)) in aliases.iter() {
expose_alias_to_host(
env,

View file

@ -14,7 +14,8 @@ use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
use roc_region::all::Region;
use super::build::{
add_func, load_roc_value, load_symbol_and_layout, use_roc_value, FunctionSpec, Scope,
add_func, load_roc_value, load_symbol_and_layout, use_roc_value, FunctionSpec, LlvmBackendMode,
Scope,
};
#[derive(Debug, Clone, Copy)]
@ -93,6 +94,16 @@ fn write_state<'a, 'ctx, 'env>(
env.builder.build_store(offset_ptr, offset);
}
pub(crate) fn finalize(env: &Env) {
let func = env
.module
.get_function(bitcode::UTILS_EXPECT_FAILED_FINALIZE)
.unwrap();
env.builder
.build_call(func, &[], "call_expect_failed_finalize");
}
pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
@ -101,10 +112,13 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
region: Region,
lookups: &[Symbol],
) {
let func = env
.module
.get_function(bitcode::UTILS_EXPECT_FAILED_START)
.unwrap();
let start_function = if let LlvmBackendMode::BinaryDev = env.mode {
bitcode::UTILS_EXPECT_FAILED_START_SHARED_FILE
} else {
bitcode::UTILS_EXPECT_FAILED_START_SHARED_BUFFER
};
let func = env.module.get_function(start_function).unwrap();
let call_result = env
.builder

View file

@ -155,10 +155,32 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) {
}
}
unreachable_function(env, "roc_getppid");
unreachable_function(env, "roc_mmap");
unreachable_function(env, "roc_send_signal");
unreachable_function(env, "roc_shm_open");
add_sjlj_roc_panic(env)
}
}
fn unreachable_function(env: &Env, name: &str) {
// The type of this function (but not the implementation) should have
// already been defined by the builtins, which rely on it.
let fn_val = env.module.get_function(name).unwrap();
// Add a basic block for the entry point
let entry = env.context.append_basic_block(fn_val, "entry");
env.builder.position_at_end(entry);
env.builder.build_unreachable();
if cfg!(debug_assertions) {
crate::llvm::build::verify_fn(fn_val);
}
}
pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
let ctx = env.context;
let module = env.module;

View file

@ -120,7 +120,11 @@ macro_rules! run_jit_function {
$transform(success)
}
Err(error_msg) => panic!("Roc failed with message: {}", error_msg),
Err(error_msg) => {
eprintln!("This Roc code crashed with: \"{error_msg}\"");
Expr::MalformedClosure
}
}
}};
}

View file

@ -17,8 +17,8 @@ const SKIP_SUBS_CACHE: bool = {
pub use roc_load_internal::docs;
pub use roc_load_internal::file::{
EntryPoint, ExecutionMode, Expectations, LoadConfig, LoadResult, LoadStart, LoadedModule,
LoadingProblem, MonomorphizedModule, Phase, Threading,
EntryPoint, ExecutionMode, ExpectMetadata, Expectations, LoadConfig, LoadResult, LoadStart,
LoadedModule, LoadingProblem, MonomorphizedModule, Phase, Threading,
};
#[allow(clippy::too_many_arguments)]

View file

@ -705,6 +705,13 @@ pub struct MonomorphizedModule<'a> {
pub expectations: VecMap<ModuleId, Expectations>,
}
/// Values used to render expect output
pub struct ExpectMetadata<'a> {
pub interns: Interns,
pub layout_interner: SingleThreadedInterner<'a, Layout<'a>>,
pub expectations: VecMap<ModuleId, Expectations>,
}
#[derive(Debug)]
pub enum EntryPoint<'a> {
Executable {

View file

@ -615,6 +615,14 @@ impl<'a> CommentOrNewline<'a> {
DocComment(comment_str) => format!("##{}", comment_str),
}
}
pub fn comment_str(&'a self) -> Option<&'a str> {
match self {
CommentOrNewline::LineComment(s) => Some(*s),
CommentOrNewline::DocComment(s) => Some(*s),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]

View file

@ -56,13 +56,13 @@ pub struct ExprParseOptions {
/// This is usually true, but false within list/record literals
/// because the comma separating backpassing arguments conflicts
/// with the comma separating literal elements
accept_multi_backpassing: bool,
pub accept_multi_backpassing: bool,
/// Check for the `->` token, and raise an error if found
/// This is usually true, but false in if-guards
///
/// > Just foo if foo == 2 -> ...
check_for_arrow: bool,
pub check_for_arrow: bool,
}
impl Default for ExprParseOptions {
@ -896,6 +896,65 @@ pub fn parse_single_def<'a>(
}
}
// This is a macro only because trying to make it be a function caused lifetime issues.
#[macro_export]
macro_rules! join_ann_to_body {
($arena:expr, $loc_pattern:expr, $loc_def_expr:expr, $ann_pattern:expr, $ann_type:expr, $spaces_before_current:expr, $region:expr) => {{
// join this body with the preceding annotation
let value_def = ValueDef::AnnotatedBody {
ann_pattern: $arena.alloc(*$ann_pattern),
ann_type: $arena.alloc(*$ann_type),
comment: $spaces_before_current
.first()
.and_then($crate::ast::CommentOrNewline::comment_str),
body_pattern: $arena.alloc($loc_pattern),
body_expr: *$arena.alloc($loc_def_expr),
};
(
value_def,
roc_region::all::Region::span_across(&$ann_pattern.region, &$region),
)
}};
}
// This is a macro only because trying to make it be a function caused lifetime issues.
#[macro_export]
macro_rules! join_alias_to_body {
($arena:expr, $loc_pattern:expr, $loc_def_expr:expr, $header:expr, $ann_type:expr, $spaces_before_current:expr, $region:expr) => {{
use roc_region::all::Region;
// This is a case like
// UserId x : [UserId Int]
// UserId x = UserId 42
// We optimistically parsed the first line as an alias; we now turn it
// into an annotation.
let loc_name = $arena.alloc($header.name.map(|x| Pattern::Tag(x)));
let ann_pattern = Pattern::Apply(loc_name, $header.vars);
let vars_region = Region::across_all($header.vars.iter().map(|v| &v.region));
let region_ann_pattern = Region::span_across(&loc_name.region, &vars_region);
let loc_ann_pattern = Loc::at(region_ann_pattern, ann_pattern);
let value_def = ValueDef::AnnotatedBody {
ann_pattern: $arena.alloc(loc_ann_pattern),
ann_type: $arena.alloc(*$ann_type),
comment: $spaces_before_current
.first()
.and_then($crate::ast::CommentOrNewline::comment_str),
body_pattern: $arena.alloc($loc_pattern),
body_expr: *$arena.alloc($loc_def_expr),
};
(
value_def,
Region::span_across(&$header.name.region, &$region),
)
}};
}
fn parse_defs_end<'a>(
_options: ExprParseOptions,
min_indent: u32,
@ -920,97 +979,67 @@ fn parse_defs_end<'a>(
Either::Second(value_def) => {
// If we got a ValueDef::Body, check if a type annotation preceded it.
// If so, we may need to combine them into an AnnotatedBody.
match value_def {
let joined = match value_def {
ValueDef::Body(loc_pattern, loc_def_expr)
if spaces_before_current.len() <= 1 =>
{
let region =
Region::span_across(&loc_pattern.region, &loc_def_expr.region);
let comment = match spaces_before_current.get(0) {
Some(CommentOrNewline::LineComment(s)) => Some(*s),
Some(CommentOrNewline::DocComment(s)) => Some(*s),
_ => None,
};
match defs.last() {
Some(Err(ValueDef::Annotation(ann_pattern, ann_type))) => {
// join this body with the preceding annotation
let value_def = ValueDef::AnnotatedBody {
ann_pattern: arena.alloc(*ann_pattern),
ann_type: arena.alloc(*ann_type),
comment,
body_pattern: arena.alloc(loc_pattern),
body_expr: arena.alloc(loc_def_expr),
};
let region =
Region::span_across(&ann_pattern.region, &region);
let (value_def, region) = join_ann_to_body!(
arena,
loc_pattern,
loc_def_expr,
ann_pattern,
ann_type,
spaces_before_current,
region
);
defs.replace_with_value_def(
defs.tags.len() - 1,
value_def,
region,
)
);
true
}
Some(Ok(TypeDef::Alias {
header,
ann: ann_type,
})) => {
// This is a case like
// UserId x : [UserId Int]
// UserId x = UserId 42
// We optimistically parsed the first line as an alias; we now turn it
// into an annotation.
let loc_name =
arena.alloc(header.name.map(|x| Pattern::Tag(x)));
let ann_pattern = Pattern::Apply(loc_name, header.vars);
let vars_region = Region::across_all(
header.vars.iter().map(|v| &v.region),
let (value_def, region) = join_alias_to_body!(
arena,
loc_pattern,
loc_def_expr,
header,
ann_type,
spaces_before_current,
region
);
let region_ann_pattern =
Region::span_across(&loc_name.region, &vars_region);
let loc_ann_pattern =
Loc::at(region_ann_pattern, ann_pattern);
let value_def = ValueDef::AnnotatedBody {
ann_pattern: arena.alloc(loc_ann_pattern),
ann_type: arena.alloc(*ann_type),
comment,
body_pattern: arena.alloc(loc_pattern),
body_expr: arena.alloc(loc_def_expr),
};
let region =
Region::span_across(&header.name.region, &region);
defs.replace_with_value_def(
defs.tags.len() - 1,
value_def,
region,
)
}
_ => {
// the previous and current def can't be joined up
defs.push_value_def(
value_def,
region,
spaces_before_current,
&[],
);
true
}
_ => false,
}
}
}
_ => {
_ => false,
};
if !joined {
// the previous and current def can't be joined up
defs.push_value_def(value_def, region, spaces_before_current, &[]);
}
}
}
}
next_state
}
@ -1021,9 +1050,9 @@ fn parse_defs_end<'a>(
}
pub struct SingleDef<'a> {
type_or_value: Either<TypeDef<'a>, ValueDef<'a>>,
region: Region,
spaces_before: &'a [CommentOrNewline<'a>],
pub type_or_value: Either<TypeDef<'a>, ValueDef<'a>>,
pub region: Region,
pub spaces_before: &'a [CommentOrNewline<'a>],
}
fn parse_defs_expr<'a>(

View file

@ -190,6 +190,150 @@ pub enum Problem {
},
}
impl Problem {
/// Returns a Region value from the Problem, if possible.
/// Some problems have more than one region; in those cases,
/// this tries to pick the one that's closest to the original
/// definition site, since that's what the REPL uses this for:
/// filtering out errors and warnings from wrapped defs based
/// on their Region being outside the expression currently being evaluated.
pub fn region(&self) -> Option<Region> {
match self {
Problem::UnusedDef(_, region)
| Problem::Shadowing {
original_region: region,
..
}
| Problem::UnusedImport(_, region)
| Problem::UnusedModuleImport(_, region)
| Problem::UnknownGeneratesWith(Loc { region, .. })
| Problem::UnusedArgument(_, _, _, region)
| Problem::UnusedBranchDef(_, region)
| Problem::PrecedenceProblem(PrecedenceProblem::BothNonAssociative(region, _, _))
| Problem::UnsupportedPattern(_, region)
| Problem::CyclicAlias(_, region, _, _)
| Problem::PhantomTypeArgument {
variable_region: region,
..
}
| Problem::UnboundTypeVariable {
one_occurrence: region,
..
}
| Problem::DuplicateRecordFieldValue {
record_region: region,
..
}
| Problem::DuplicateRecordFieldType {
record_region: region,
..
}
| Problem::InvalidOptionalValue {
record_region: region,
..
}
| Problem::DuplicateTag {
tag_union_region: region,
..
}
| Problem::RuntimeError(RuntimeError::Shadowing {
original_region: region,
..
})
| Problem::RuntimeError(RuntimeError::InvalidOptionalValue {
record_region: region,
..
})
| Problem::RuntimeError(RuntimeError::UnsupportedPattern(region))
| Problem::RuntimeError(RuntimeError::MalformedPattern(_, region))
| Problem::RuntimeError(RuntimeError::LookupNotInScope(Loc { region, .. }, _))
| Problem::RuntimeError(RuntimeError::OpaqueNotDefined {
usage: Loc { region, .. },
..
})
| Problem::RuntimeError(RuntimeError::OpaqueOutsideScope {
referenced_region: region,
..
})
| Problem::RuntimeError(RuntimeError::OpaqueNotApplied(Loc { region, .. }))
| Problem::RuntimeError(RuntimeError::OpaqueAppliedToMultipleArgs(region))
| Problem::RuntimeError(RuntimeError::ValueNotExposed { region, .. })
| Problem::RuntimeError(RuntimeError::ModuleNotImported { region, .. })
| Problem::RuntimeError(RuntimeError::InvalidPrecedence(_, region))
| Problem::RuntimeError(RuntimeError::MalformedIdentifier(_, _, region))
| Problem::RuntimeError(RuntimeError::MalformedTypeName(_, region))
| Problem::RuntimeError(RuntimeError::MalformedClosure(region))
| Problem::RuntimeError(RuntimeError::InvalidRecordUpdate { region })
| Problem::RuntimeError(RuntimeError::InvalidFloat(_, region, _))
| Problem::RuntimeError(RuntimeError::InvalidInt(_, _, region, _))
| Problem::RuntimeError(RuntimeError::InvalidInterpolation(region))
| Problem::RuntimeError(RuntimeError::InvalidHexadecimal(region))
| Problem::RuntimeError(RuntimeError::InvalidUnicodeCodePt(region))
| Problem::RuntimeError(RuntimeError::EmptySingleQuote(region))
| Problem::RuntimeError(RuntimeError::MultipleCharsInSingleQuote(region))
| Problem::RuntimeError(RuntimeError::DegenerateBranch(region))
| Problem::InvalidAliasRigid { region, .. }
| Problem::InvalidInterpolation(region)
| Problem::InvalidHexadecimal(region)
| Problem::InvalidUnicodeCodePt(region)
| Problem::NestedDatatype {
def_region: region, ..
}
| Problem::InvalidExtensionType { region, .. }
| Problem::AbilityHasTypeVariables {
variables_region: region,
..
}
| Problem::HasClauseIsNotAbility { region }
| Problem::IllegalHasClause { region }
| Problem::DuplicateHasAbility { region, .. }
| Problem::AbilityMemberMissingHasClause { region, .. }
| Problem::AbilityMemberMultipleBoundVars {
span_has_clauses: region,
..
}
| Problem::AbilityNotOnToplevel { region }
| Problem::AbilityUsedAsType(_, _, region)
| Problem::NestedSpecialization(_, region)
| Problem::IllegalDerivedAbility(region)
| Problem::ImplementationNotFound { region, .. }
| Problem::NotAnAbilityMember { region, .. }
| Problem::OptionalAbilityImpl { region, .. }
| Problem::QualifiedAbilityImpl { region }
| Problem::AbilityImplNotIdent { region }
| Problem::DuplicateImpl {
original: region, ..
}
| Problem::NotAnAbility(region)
| Problem::ImplementsNonRequired { region, .. }
| Problem::DoesNotImplementAbility { region, .. }
| Problem::NoIdentifiersIntroduced(region)
| Problem::OverloadedSpecialization {
overload: region, ..
}
| Problem::NotBoundInAllPatterns { region, .. }
| Problem::SignatureDefMismatch {
def_pattern: region,
..
}
| Problem::MultipleListRestPattern { region }
| Problem::UnnecessaryOutputWildcard { region } => Some(*region),
Problem::RuntimeError(RuntimeError::CircularDef(cycle_entries))
| Problem::BadRecursion(cycle_entries) => {
cycle_entries.first().map(|entry| entry.expr_region)
}
Problem::RuntimeError(RuntimeError::UnresolvedTypeVar)
| Problem::RuntimeError(RuntimeError::ErroneousType)
| Problem::RuntimeError(RuntimeError::NonExhaustivePattern)
| Problem::RuntimeError(RuntimeError::NoImplementation)
| Problem::RuntimeError(RuntimeError::VoidValue)
| Problem::RuntimeError(RuntimeError::ExposedButNotDefined(_))
| Problem::RuntimeError(RuntimeError::NoImplementationNamed { .. })
| Problem::ExposedButNotDefined(_) => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ExtensionTypeKind {
Record,

View file

@ -244,6 +244,7 @@ fn create_llvm_module<'a>(
};
let (main_fn_name, main_fn) = match config.mode {
LlvmBackendMode::Binary => unreachable!(),
LlvmBackendMode::BinaryDev => unreachable!(),
LlvmBackendMode::CliTest => unreachable!(),
LlvmBackendMode::WasmGenTest => roc_gen_llvm::llvm::build::build_wasm_test_wrapper(
&env,

View file

@ -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;

View file

@ -37,6 +37,10 @@ a {
color: #972395;
}
table tr th, table tr td {
padding: 6px 13px;
}
.logo {
padding: 2px 8px;
}

View file

@ -76,13 +76,23 @@ fn collect_roc_definitions<'a>(object: &object::File<'a, &'a [u8]>) -> MutMap<St
let address = sym.address() as u64;
// special exceptions for memcpy and memset.
if name == "roc_memcpy" {
vaddresses.insert("memcpy".to_string(), address);
} else if name == "roc_memset" {
vaddresses.insert("memset".to_string(), address);
} else if name == "roc_memmove" {
vaddresses.insert("memmove".to_string(), address);
// special exceptions for roc_ functions that map to libc symbols
let direct_mapping = match name {
"roc_memcpy" => Some("memcpy"),
"roc_memset" => Some("memset"),
"roc_memmove" => Some("memmove"),
// for expects
"roc_mmap" => Some("mmap"),
"roc_getppid" => Some("getppid"),
"roc_send_signal" => Some("kill"),
"roc_shm_open" => Some("shm_open"),
_ => None,
};
if let Some(libc_symbol) = direct_mapping {
vaddresses.insert(libc_symbol.to_string(), address);
}
vaddresses.insert(name.to_string(), address);

View file

@ -23,6 +23,7 @@ libloading = "0.7.1"
rustyline = {git = "https://github.com/roc-lang/rustyline", rev = "e74333c"}
rustyline-derive = {git = "https://github.com/roc-lang/rustyline", rev = "e74333c"}
target-lexicon = "0.12.2"
unicode-segmentation = "1.10.0"
roc_build = {path = "../compiler/build"}
roc_builtins = {path = "../compiler/builtins"}

View 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))
}

View 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";

View file

@ -1,38 +1,13 @@
//! Command Line Interface (CLI) functionality for the Read-Evaluate-Print-Loop (REPL).
use bumpalo::Bump;
mod cli_gen;
mod colors;
pub mod repl_state;
use colors::{BLUE, END_COL, PINK};
use const_format::concatcp;
use inkwell::context::Context;
use libloading::Library;
use roc_gen_llvm::llvm::build::LlvmBackendMode;
use roc_intern::SingleThreadedInterner;
use roc_mono::layout::Layout;
use roc_types::subs::Subs;
use rustyline::highlight::{Highlighter, PromptInfo};
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
use rustyline_derive::{Completer, Helper, Hinter};
use std::borrow::Cow;
use std::io;
use target_lexicon::Triple;
use repl_state::ReplState;
use roc_build::link::llvm_module_to_dylib;
use roc_collections::all::MutSet;
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type};
use roc_load::{EntryPoint, MonomorphizedModule};
use roc_mono::ir::OptLevel;
use roc_parse::ast::Expr;
use roc_parse::parser::{EClosure, EExpr, SyntaxError};
use roc_repl_eval::eval::jit_to_ast;
use roc_repl_eval::gen::{compile_to_mono, format_answer, ReplOutput};
use roc_repl_eval::{ReplApp, ReplAppMemory};
use roc_reporting::report::DEFAULT_PALETTE;
use roc_std::RocStr;
use roc_target::TargetInfo;
use roc_types::pretty_print::{name_and_print_var, DebugPrint};
const BLUE: &str = "\u{001b}[36m";
const PINK: &str = "\u{001b}[35m";
const END_COL: &str = "\u{001b}[0m";
use crate::repl_state::PROMPT;
pub const WELCOME_MESSAGE: &str = concatcp!(
"\n The rockin ",
@ -50,480 +25,54 @@ pub const WELCOME_MESSAGE: &str = concatcp!(
// TODO add link to repl tutorial(does not yet exist).
pub const SHORT_INSTRUCTIONS: &str = "Enter an expression, or :help, or :q to quit.\n\n";
// TODO add link to repl tutorial(does not yet exist).
pub const TIPS: &str = concatcp!(
BLUE,
" - ",
END_COL,
"Entered code needs to return something. For example:\n\n",
PINK,
" » foo = 1\n … foo\n\n",
END_COL,
BLUE,
" - ",
END_COL,
"You can use `Ctrl+V`+`Ctrl+J` to make a newline. The repl will also insert a newline if you press enter",
" when the current expression can not be evaluated, e.g. when you type `foo =<ENTER>`.\n\n",
BLUE,
" - ",
END_COL,
":q to quit\n\n",
BLUE,
" - ",
END_COL,
":help\n"
);
pub const PROMPT: &str = concatcp!("\n", BLUE, "»", END_COL, " ");
pub const CONT_PROMPT: &str = concatcp!(BLUE, "", END_COL, " ");
#[derive(Completer, Helper, Hinter)]
struct ReplHelper {
validator: InputValidator,
pending_src: String,
}
impl ReplHelper {
pub(crate) fn new() -> ReplHelper {
ReplHelper {
validator: InputValidator::new(),
pending_src: String::new(),
}
}
}
impl Highlighter for ReplHelper {
fn has_continuation_prompt(&self) -> bool {
true
}
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
info: PromptInfo<'_>,
) -> Cow<'b, str> {
if info.line_no() > 0 {
CONT_PROMPT.into()
} else {
prompt.into()
}
}
}
impl Validator for ReplHelper {
fn validate(
&self,
ctx: &mut validate::ValidationContext,
) -> rustyline::Result<validate::ValidationResult> {
self.validator.validate(ctx)
}
fn validate_while_typing(&self) -> bool {
self.validator.validate_while_typing()
}
}
struct InputValidator {}
impl InputValidator {
pub(crate) fn new() -> InputValidator {
InputValidator {}
}
}
impl Validator for InputValidator {
fn validate(&self, ctx: &mut ValidationContext) -> rustyline::Result<ValidationResult> {
if ctx.input().is_empty() {
Ok(ValidationResult::Incomplete)
} else {
let arena = bumpalo::Bump::new();
let state = roc_parse::state::State::new(ctx.input().trim().as_bytes());
match roc_parse::expr::parse_loc_expr(&arena, state, 0) {
// Special case some syntax errors to allow for multi-line inputs
Err((_, EExpr::DefMissingFinalExpr(_), _))
| Err((_, EExpr::DefMissingFinalExpr2(_, _), _))
| Err((_, EExpr::Closure(EClosure::Body(_, _), _), _)) => {
Ok(ValidationResult::Incomplete)
}
_ => Ok(ValidationResult::Valid(None)),
}
}
}
}
struct CliApp {
lib: Library,
}
struct CliMemory;
impl<'a> ReplApp<'a> for CliApp {
type Memory = CliMemory;
/// Run user code that returns a type with a `Builtin` layout
/// Size of the return value is statically determined from its Rust type
fn call_function<Return, F>(&mut self, main_fn_name: &str, mut transform: F) -> Expr<'a>
where
F: FnMut(&'a Self::Memory, Return) -> Expr<'a>,
Self::Memory: 'a,
{
run_jit_function!(self.lib, main_fn_name, Return, |v| transform(&CliMemory, v))
}
/// Run user code that returns a struct or union, whose size is provided as an argument
fn call_function_dynamic_size<T, F>(
&mut self,
main_fn_name: &str,
ret_bytes: usize,
mut transform: F,
) -> T
where
F: FnMut(&'a Self::Memory, usize) -> T,
Self::Memory: 'a,
{
run_jit_function_dynamic_type!(self.lib, main_fn_name, ret_bytes, |v| transform(
&CliMemory, v
))
}
}
macro_rules! deref_number {
($name: ident, $t: ty) => {
fn $name(&self, addr: usize) -> $t {
let ptr = addr as *const _;
unsafe { *ptr }
}
};
}
impl ReplAppMemory for CliMemory {
deref_number!(deref_bool, bool);
deref_number!(deref_u8, u8);
deref_number!(deref_u16, u16);
deref_number!(deref_u32, u32);
deref_number!(deref_u64, u64);
deref_number!(deref_u128, u128);
deref_number!(deref_usize, usize);
deref_number!(deref_i8, i8);
deref_number!(deref_i16, i16);
deref_number!(deref_i32, i32);
deref_number!(deref_i64, i64);
deref_number!(deref_i128, i128);
deref_number!(deref_isize, isize);
deref_number!(deref_f32, f32);
deref_number!(deref_f64, f64);
fn deref_str(&self, addr: usize) -> &str {
let reference: &RocStr = unsafe { std::mem::transmute(addr) };
reference.as_str()
}
fn deref_pointer_with_tag_id(&self, addr: usize) -> (u16, u64) {
let addr_with_id = self.deref_usize(addr);
let tag_id_mask = 0b111;
let tag_id = addr_with_id & tag_id_mask;
let data_addr = addr_with_id & !tag_id_mask;
(tag_id as _, data_addr as _)
}
}
pub fn mono_module_to_dylib<'a>(
arena: &'a Bump,
target: Triple,
loaded: MonomorphizedModule<'a>,
opt_level: OptLevel,
) -> Result<
(
libloading::Library,
&'a str,
Subs,
SingleThreadedInterner<'a, Layout<'a>>,
),
libloading::Error,
> {
let target_info = TargetInfo::from(&target);
let MonomorphizedModule {
procedures,
entry_point,
interns,
subs,
layout_interner,
..
} = loaded;
let context = Context::create();
let builder = context.create_builder();
let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins(
&target, &context, "",
));
let module = arena.alloc(module);
let (module_pass, function_pass) =
roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level);
let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module);
// Compile and add all the Procs before adding main
let env = roc_gen_llvm::llvm::build::Env {
arena,
layout_interner: &layout_interner,
builder: &builder,
dibuilder: &dibuilder,
compile_unit: &compile_unit,
context: &context,
interns,
module,
target_info,
mode: LlvmBackendMode::GenTest, // so roc_panic is generated
// important! we don't want any procedures to get the C calling convention
exposed_to_host: MutSet::default(),
};
// Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no
// platform to provide them.
add_default_roc_externs(&env);
let entry_point = match entry_point {
EntryPoint::Executable { symbol, layout, .. } => {
roc_mono::ir::EntryPoint { symbol, layout }
}
EntryPoint::Test => {
unreachable!()
}
};
let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main(
&env,
opt_level,
procedures,
entry_point,
);
env.dibuilder.finalize();
// we don't use the debug info, and it causes weird errors.
module.strip_debug_info();
// Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr();
if main_fn.verify(true) {
function_pass.run_on(&main_fn);
} else {
panic!("Main function {} failed LLVM verification in build. Uncomment things nearby to see more details.", main_fn_name);
}
module_pass.run_on(env.module);
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
// Verify the module
if let Err(errors) = env.module.verify() {
panic!(
"Errors defining module:\n{}\n\nUncomment things nearby to see more details.",
errors.to_string()
);
}
llvm_module_to_dylib(env.module, &target, opt_level)
.map(|lib| (lib, main_fn_name, subs, layout_interner))
}
fn gen_and_eval_llvm<'a>(
src: &str,
target: Triple,
opt_level: OptLevel,
) -> Result<ReplOutput, SyntaxError<'a>> {
let arena = Bump::new();
let target_info = TargetInfo::from(&target);
let mut loaded = match compile_to_mono(&arena, src, target_info, DEFAULT_PALETTE) {
Ok(x) => x,
Err(prob_strings) => {
return Ok(ReplOutput::Problems(prob_strings));
}
};
debug_assert_eq!(loaded.exposed_to_host.values.len(), 1);
let (main_fn_symbol, main_fn_var) = loaded.exposed_to_host.values.iter().next().unwrap();
let main_fn_symbol = *main_fn_symbol;
let main_fn_var = *main_fn_var;
// pretty-print the expr type string for later.
let expr_type_str = name_and_print_var(
main_fn_var,
&mut loaded.subs,
loaded.module_id,
&loaded.interns,
DebugPrint::NOTHING,
);
let (_, main_fn_layout) = match loaded.procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
Some(layout) => *layout,
None => {
return Ok(ReplOutput::NoProblems {
expr: "<function>".to_string(),
expr_type: expr_type_str,
});
}
};
let interns = loaded.interns.clone();
let (lib, main_fn_name, subs, layout_interner) =
mono_module_to_dylib(&arena, target, loaded, opt_level).expect("we produce a valid Dylib");
let mut app = CliApp { lib };
let res_answer = jit_to_ast(
&arena,
&mut app,
main_fn_name,
main_fn_layout,
main_fn_var,
&subs,
&interns,
layout_interner.into_global().fork(),
target_info,
);
let formatted = format_answer(&arena, res_answer, expr_type_str);
Ok(formatted)
}
fn eval_and_format<'a>(src: &str) -> Result<String, SyntaxError<'a>> {
let format_output = |output| match output {
ReplOutput::NoProblems { expr, expr_type } => {
format!("\n{} {}:{} {}", expr, PINK, END_COL, expr_type)
}
ReplOutput::Problems(lines) => format!("\n{}\n", lines.join("\n\n")),
};
gen_and_eval_llvm(src, Triple::host(), OptLevel::Normal).map(format_output)
}
fn report_parse_error(fail: SyntaxError) {
println!("TODO Gracefully report parse error in repl: {:?}", fail);
}
pub fn main() -> io::Result<()> {
pub fn main() -> i32 {
use rustyline::error::ReadlineError;
use rustyline::Editor;
// To debug rustyline:
// <UNCOMMENT> env_logger::init();
// <RUN WITH:> RUST_LOG=rustyline=debug cargo run repl 2> debug.log
print!("{}{}", WELCOME_MESSAGE, TIPS);
print!("{}{}", WELCOME_MESSAGE, SHORT_INSTRUCTIONS);
let mut prev_line_blank = false;
let mut editor = Editor::<ReplHelper>::new();
let repl_helper = ReplHelper::new();
let mut editor = Editor::<ReplState>::new();
let repl_helper = ReplState::new();
editor.set_helper(Some(repl_helper));
loop {
let readline = editor.readline(PROMPT);
match readline {
match editor.readline(PROMPT) {
Ok(line) => {
let trim_line = line.trim();
editor.add_history_entry(trim_line);
editor.add_history_entry(line.trim());
let pending_src = &mut editor
.helper_mut()
.expect("Editor helper was not set")
.pending_src;
let dimensions = editor.dimensions();
let repl_helper = editor.helper_mut().expect("Editor helper was not set");
match trim_line.to_lowercase().as_str() {
"" => {
if pending_src.is_empty() {
print!("\n{}", SHORT_INSTRUCTIONS);
} else if prev_line_blank {
// After two blank lines in a row, give up and try parsing it
// even though it's going to fail. This way you don't get stuck.
match eval_and_format(pending_src.as_str()) {
match repl_helper.step(&line, dimensions) {
Ok(output) => {
// If there was no output, don't print a blank line!
// (This happens for something like a type annotation.)
if !output.is_empty() {
println!("{}", output);
}
Err(fail) => {
report_parse_error(fail);
}
}
pending_src.clear();
} else {
pending_src.push('\n');
prev_line_blank = true;
continue; // Skip the part where we reset prev_line_blank to false
}
}
":help" => {
// TODO add link to repl tutorial(does not yet exist).
println!("Use :q to exit.");
}
":exit" | ":quit" | ":q" => {
break;
}
_ => {
let result = if pending_src.is_empty() {
eval_and_format(trim_line)
} else {
pending_src.push('\n');
pending_src.push_str(trim_line);
eval_and_format(pending_src.as_str())
Err(exit_code) => return exit_code,
};
match result {
Ok(output) => {
println!("{}", output);
pending_src.clear();
}
// Err(Fail {
// reason: FailReason::Eof(_),
// ..
// }) => {}
Err(fail) => {
report_parse_error(fail);
pending_src.clear();
}
}
}
}
}
Err(ReadlineError::Interrupted) => {
println!("CTRL-C");
break;
#[cfg(windows)]
Err(ReadlineError::WindowResize) => {
// This is fine; just ignore it.
}
Err(ReadlineError::Eof) => {
// If we hit an eof, and we're allowed to keep going,
// append the str to the src we're building up and continue.
// (We only need to append it here if it was empty before;
// otherwise, we already appended it before calling eval_and_format.)
let pending_src = &mut editor
.helper_mut()
.expect("Editor helper was not set")
.pending_src;
if pending_src.is_empty() {
pending_src.push_str("");
// End of input; we're done!
return 0;
}
break;
Err(ReadlineError::Interrupted) => {
eprintln!("CTRL-C");
return 1;
}
Err(err) => {
eprintln!("REPL error: {:?}", err);
break;
return 1;
}
}
prev_line_blank = false;
}
Ok(())
}

View 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
}

View file

@ -13,7 +13,7 @@ use roc_mono::layout::{
self, union_sorted_tags_pub, Builtin, Layout, LayoutCache, LayoutInterner, UnionLayout,
UnionVariant, WrappedVariant,
};
use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral};
use roc_parse::ast::{AssignedField, Collection, Expr, Pattern, StrLiteral};
use roc_region::all::{Loc, Region};
use roc_std::RocDec;
use roc_target::TargetInfo;
@ -29,11 +29,6 @@ struct Env<'a, 'env> {
layout_cache: LayoutCache<'a>,
}
#[derive(Debug)]
pub enum ToAstProblem {
FunctionLayout,
}
/// JIT execute the given main function, and then wrap its results in an Expr
/// so we can display them to the user using the formatter.
///
@ -53,7 +48,7 @@ pub fn jit_to_ast<'a, A: ReplApp<'a>>(
interns: &'a Interns,
layout_interner: LayoutInterner<'a>,
target_info: TargetInfo,
) -> Result<Expr<'a>, ToAstProblem> {
) -> Expr<'a> {
let mut env = Env {
arena,
subs,
@ -68,10 +63,24 @@ pub fn jit_to_ast<'a, A: ReplApp<'a>>(
result,
captures_niche: _,
} => {
// this is a thunk
// This is a thunk, which cannot be defined in userspace, so we know
// it's `main` and can be executed.
jit_to_ast_help(&mut env, app, main_fn_name, &result, var)
}
_ => Err(ToAstProblem::FunctionLayout),
ProcLayout { arguments, .. } => {
// This is a user-supplied function; create a fake Expr for it.
let mut arg_patterns =
bumpalo::collections::Vec::with_capacity_in(arguments.len(), arena);
// Put in an underscore for each of the args, just to get the arity right.
for _ in 0..arguments.len() {
arg_patterns.push(Loc::at_zero(Pattern::Underscore("_")));
}
let body_expr = Loc::at_zero(Expr::Record(Collection::empty()));
Expr::Closure(arg_patterns.into_bump_slice(), arena.alloc(body_expr))
}
}
}
@ -331,7 +340,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
main_fn_name: &str,
layout: &Layout<'a>,
var: Variable,
) -> Result<Expr<'a>, ToAstProblem> {
) -> Expr<'a> {
let (newtype_containers, alias_content, raw_var) = unroll_newtypes_and_aliases(env, var);
macro_rules! num_helper {
@ -342,18 +351,17 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
};
}
let result = match layout {
Layout::Builtin(Builtin::Bool) => Ok(app.call_function(
main_fn_name,
|_mem: &A::Memory, num: bool| {
let expr = match layout {
Layout::Builtin(Builtin::Bool) => {
app.call_function(main_fn_name, |_mem: &A::Memory, num: bool| {
bool_to_ast(env, num, env.subs.get_content_without_compacting(raw_var))
},
)),
})
}
Layout::Builtin(Builtin::Int(int_width)) => {
use Content::*;
use IntWidth::*;
let result = match (alias_content, int_width) {
match (alias_content, int_width) {
(Some(Alias(Symbol::NUM_UNSIGNED8, ..)), U8) => num_helper!(u8),
(_, U8) => {
// This is not a number, it's a tag union or something else
@ -371,22 +379,18 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
(_, I32) => num_helper!(i32),
(_, I64) => num_helper!(i64),
(_, I128) => num_helper!(i128),
};
Ok(result)
}
}
Layout::Builtin(Builtin::Float(float_width)) => {
use FloatWidth::*;
let result = match float_width {
match float_width {
F32 => num_helper!(f32),
F64 => num_helper!(f64),
F128 => todo!("F128 not implemented"),
};
Ok(result)
}
Layout::Builtin(Builtin::Decimal) => Ok(num_helper!(RocDec)),
}
Layout::Builtin(Builtin::Decimal) => num_helper!(RocDec),
Layout::Builtin(Builtin::Str) => {
let body = |mem: &A::Memory, addr| {
let string = mem.deref_str(addr);
@ -394,11 +398,9 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
Expr::Str(StrLiteral::PlainLine(arena_str))
};
Ok(app.call_function_returns_roc_str(env.target_info, main_fn_name, body))
app.call_function_returns_roc_str(env.target_info, main_fn_name, body)
}
Layout::Builtin(Builtin::List(elem_layout)) => {
//
Ok(app.call_function_returns_roc_list(
Layout::Builtin(Builtin::List(elem_layout)) => app.call_function_returns_roc_list(
main_fn_name,
|mem: &A::Memory, (addr, len, _cap)| {
list_to_ast(
@ -410,8 +412,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
env.subs.get_content_without_compacting(raw_var),
)
},
))
}
),
Layout::Struct { field_layouts, .. } => {
let fields = [Layout::u64(), *layout];
let layout = Layout::struct_no_name_order(env.arena.alloc(fields));
@ -423,38 +424,24 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
.get_content_without_compacting(raw_var)
{
Content::Structure(FlatType::Record(fields, _)) => {
Ok(struct_to_ast(env, mem, addr, *fields))
struct_to_ast(env, mem, addr, *fields)
}
Content::Structure(FlatType::EmptyRecord) => {
Ok(struct_to_ast(env, mem, addr, RecordFields::empty()))
struct_to_ast(env, mem, addr, RecordFields::empty())
}
Content::Structure(FlatType::TagUnion(tags, _)) => {
let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags);
Ok(single_tag_union_to_ast(
env,
mem,
addr,
field_layouts,
tag_name,
payload_vars,
))
single_tag_union_to_ast(env, mem, addr, field_layouts, tag_name, payload_vars)
}
Content::Structure(FlatType::FunctionOrTagUnion(tag_names, _, _)) => {
let tag_name = &env.subs.get_subs_slice(*tag_names)[0];
Ok(single_tag_union_to_ast(
env,
mem,
addr,
field_layouts,
tag_name,
&[],
))
single_tag_union_to_ast(env, mem, addr, field_layouts, tag_name, &[])
}
Content::Structure(FlatType::Func(_, _, _)) => {
// a function with a struct as the closure environment
Ok(OPAQUE_FUNCTION)
OPAQUE_FUNCTION
}
other => {
unreachable!(
@ -472,7 +459,8 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
}
Layout::Union(UnionLayout::NonRecursive(_)) => {
let size = layout.stack_size(&env.layout_cache.interner, env.target_info);
Ok(app.call_function_dynamic_size(
app.call_function_dynamic_size(
main_fn_name,
size as usize,
|mem: &'a A::Memory, addr: usize| {
@ -485,14 +473,15 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
env.subs.get_root_key_without_compacting(raw_var),
)
},
))
)
}
Layout::Union(UnionLayout::Recursive(_))
| Layout::Union(UnionLayout::NonNullableUnwrapped(_))
| Layout::Union(UnionLayout::NullableUnwrapped { .. })
| Layout::Union(UnionLayout::NullableWrapped { .. }) => {
let size = layout.stack_size(&env.layout_cache.interner, env.target_info);
Ok(app.call_function_dynamic_size(
app.call_function_dynamic_size(
main_fn_name,
size as usize,
|mem: &'a A::Memory, addr: usize| {
@ -505,18 +494,16 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
env.subs.get_root_key_without_compacting(raw_var),
)
},
))
)
}
Layout::RecursivePointer => {
unreachable!("RecursivePointers can only be inside structures")
}
Layout::LambdaSet(_) => Ok(OPAQUE_FUNCTION),
Layout::LambdaSet(_) => OPAQUE_FUNCTION,
Layout::Boxed(_) => {
let size = layout.stack_size(&env.layout_cache.interner, env.target_info);
Ok(app.call_function_dynamic_size(
main_fn_name,
size as usize,
|mem: &A::Memory, addr| {
app.call_function_dynamic_size(main_fn_name, size as usize, |mem: &A::Memory, addr| {
addr_to_ast(
env,
mem,
@ -525,11 +512,11 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
WhenRecursive::Unreachable,
env.subs.get_root_key_without_compacting(raw_var),
)
},
))
})
}
};
result.map(|e| apply_newtypes(env, newtype_containers.into_bump_slice(), e))
apply_newtypes(env, newtype_containers.into_bump_slice(), expr)
}
fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> {

View file

@ -1,6 +1,6 @@
use bumpalo::Bump;
use roc_load::{ExecutionMode, LoadConfig, Threading};
use roc_reporting::report::Palette;
use roc_reporting::report::{Palette, Severity};
use std::path::PathBuf;
use roc_fmt::annotation::Formattable;
@ -11,48 +11,47 @@ use roc_region::all::LineInfo;
use roc_reporting::report::{can_problem, type_problem, RocDocAllocator};
use roc_target::TargetInfo;
use crate::eval::ToAstProblem;
pub enum ReplOutput {
Problems(Vec<String>),
NoProblems { expr: String, expr_type: String },
#[derive(Debug)]
pub struct ReplOutput {
pub expr: String,
pub expr_type: String,
}
pub fn format_answer(
arena: &Bump,
res_answer: Result<Expr, ToAstProblem>,
expr_type_str: String,
) -> ReplOutput {
pub fn format_answer<'a>(arena: &'a Bump, answer: Expr<'_>) -> &'a str {
match answer {
Expr::Closure(_, _) | Expr::MalformedClosure => "<function>",
_ => {
let mut expr = roc_fmt::Buf::new_in(arena);
use ToAstProblem::*;
match res_answer {
Ok(answer) => {
answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0);
expr.into_bump_str()
}
Err(FunctionLayout) => {
expr.indent(0);
expr.push_str("<function>");
}
}
ReplOutput::NoProblems {
expr: expr.into_bump_str().to_string(),
expr_type: expr_type_str,
#[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>(
pub fn compile_to_mono<'a, 'i, I: Iterator<Item = &'i str>>(
arena: &'a Bump,
src: &str,
defs: I,
expr: &str,
target_info: TargetInfo,
palette: Palette,
) -> Result<MonomorphizedModule<'a>, Vec<String>> {
) -> (Option<MonomorphizedModule<'a>>, Problems) {
let filename = PathBuf::from("");
let src_dir = PathBuf::from("fake/test/path");
let module_src = arena.alloc(promote_expr_to_module(src));
let (bytes_before_expr, module_src) = promote_expr_to_module(arena, defs, expr);
let exposed_types = Default::default();
let loaded = roc_load::load_and_monomorphize_from_str(
arena,
@ -71,10 +70,16 @@ pub fn compile_to_mono<'a>(
let mut loaded = match loaded {
Ok(v) => v,
Err(LoadingProblem::FormattedReport(report)) => {
return Err(vec![report]);
return (
None,
Problems {
errors: vec![report],
warnings: Vec::new(),
},
);
}
Err(e) => {
panic!("error while loading module: {:?}", e)
todo!("error while loading module: {:?}", e)
}
};
@ -86,7 +91,10 @@ pub fn compile_to_mono<'a>(
..
} = &mut loaded;
let mut lines = Vec::new();
let mut problems = Problems::default();
let errors = &mut problems.errors;
let warnings = &mut problems.warnings;
for (home, (module_path, src)) in sources.iter() {
let can_probs = can_problems.remove(home).unwrap_or_default();
@ -105,42 +113,77 @@ pub fn compile_to_mono<'a>(
let alloc = RocDocAllocator::new(&src_lines, *home, interns);
for problem in can_probs.into_iter() {
// Filter out all warnings and errors whose regions end before this,
// because they must be part of the defs (excluding the most renently added def,
// if that's the one being evaluated) and therefore not things we should show.
// This filters out things like shadowing warnings and unused def warnings.
if problem.region().unwrap_or_default().end().offset as usize >= bytes_before_expr {
let report = can_problem(&alloc, &line_info, module_path.clone(), problem);
let severity = report.severity;
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf);
match severity {
Severity::Warning => {
warnings.push(buf);
}
Severity::RuntimeError => {
errors.push(buf);
}
}
}
}
for problem in type_probs {
if let Some(report) = type_problem(&alloc, &line_info, module_path.clone(), problem) {
let severity = report.severity;
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf);
match severity {
Severity::Warning => {
warnings.push(buf);
}
Severity::RuntimeError => {
errors.push(buf);
}
}
}
if !lines.is_empty() {
Err(lines)
} else {
Ok(loaded)
}
}
fn promote_expr_to_module(src: &str) -> String {
let mut buffer =
String::from("app \"app\" provides [replOutput] to \"./platform\"\n\nreplOutput =\n");
(Some(loaded), problems)
}
for line in src.lines() {
// indent the body!
buffer.push_str(" ");
fn promote_expr_to_module<'a, 'i, I: Iterator<Item = &'i str>>(
arena: &'a Bump,
defs: I,
expr: &str,
) -> (usize, &'a str) {
const REPL_MODULE_HEADER: &str = "app \"app\" provides [replOutput] to \"./platform\"\n\n";
const REPL_MODULE_MAIN_DEF: &str = "replOutput =\n";
const INDENT: &str = " ";
let mut buffer = bumpalo::collections::string::String::from_str_in(REPL_MODULE_HEADER, arena);
for line in defs {
// don't indent the defs
buffer.push_str(line);
buffer.push_str("\n\n");
}
buffer.push_str(REPL_MODULE_MAIN_DEF);
let bytes_before_expr = buffer.len();
for line in expr.lines() {
// indent the expr!
buffer.push_str(INDENT);
buffer.push_str(line);
buffer.push('\n');
}
buffer
(bytes_before_expr, buffer.into_bump_str())
}

View file

@ -26,6 +26,7 @@ roc_types = {path = "../compiler/types"}
roc_gen_llvm = {path = "../compiler/gen_llvm"}
roc_region = { path = "../compiler/region" }
roc_build = { path = "../compiler/build" }
roc_error_macros = { path = "../error_macros" }
libloading = "0.7.1"
inkwell = { path = "../vendor/inkwell" }

View file

@ -8,10 +8,7 @@ use {
layout::{CapturesNiche, Layout, LayoutCache},
},
roc_parse::ast::Expr,
roc_repl_eval::{
eval::{jit_to_ast, ToAstProblem},
ReplAppMemory,
},
roc_repl_eval::{eval::jit_to_ast, ReplAppMemory},
roc_target::TargetInfo,
roc_types::subs::{Subs, Variable},
std::sync::Arc,
@ -36,7 +33,7 @@ pub fn get_values<'a>(
start: *const u8,
start_offset: usize,
variables: &[Variable],
) -> Result<(usize, Vec<Expr<'a>>), ToAstProblem> {
) -> (usize, Vec<Expr<'a>>) {
let mut result = Vec::with_capacity(variables.len());
let memory = ExpectMemory { start };
@ -75,13 +72,13 @@ pub fn get_values<'a>(
interns,
layout_interner.fork(),
target_info,
)?
)
};
result.push(expr);
}
Ok((app.offset, result))
(app.offset, result)
}
#[cfg(not(windows))]

View file

@ -6,6 +6,7 @@ use inkwell::context::Context;
use roc_build::link::llvm_module_to_dylib;
use roc_can::expr::ExpectLookup;
use roc_collections::{MutSet, VecMap};
use roc_error_macros::internal_error;
use roc_gen_llvm::{
llvm::{build::LlvmBackendMode, externs::add_default_roc_externs},
run_roc::RocCallResult,
@ -21,7 +22,7 @@ use roc_target::TargetInfo;
use roc_types::subs::{Subs, Variable};
use target_lexicon::Triple;
pub(crate) struct ExpectMemory<'a> {
pub struct ExpectMemory<'a> {
ptr: *mut u8,
length: usize,
shm_name: Option<std::ffi::CString>,
@ -41,7 +42,7 @@ impl<'a> ExpectMemory<'a> {
}
}
fn create_or_reuse_mmap(shm_name: &str) -> Self {
pub fn create_or_reuse_mmap(shm_name: &str) -> Self {
let cstring = std::ffi::CString::new(shm_name).unwrap();
Self::mmap_help(cstring, libc::O_RDWR | libc::O_CREAT)
}
@ -54,19 +55,38 @@ impl<'a> ExpectMemory<'a> {
fn mmap_help(cstring: std::ffi::CString, shm_flags: i32) -> Self {
let ptr = unsafe {
let shared_fd = libc::shm_open(cstring.as_ptr().cast(), shm_flags, 0o666);
if shared_fd == -1 {
internal_error!("failed to shm_open fd");
}
libc::ftruncate(shared_fd, Self::SHM_SIZE as _);
// NOTE: we can only call `ftruncate` once on this file descriptor on mac
if libc::ftruncate(shared_fd, Self::SHM_SIZE as _) == -1 {
internal_error!("failed to truncate shared file, are the permissions wrong?");
}
libc::mmap(
let ptr = libc::mmap(
std::ptr::null_mut(),
Self::SHM_SIZE,
libc::PROT_WRITE | libc::PROT_READ,
libc::MAP_SHARED,
shared_fd,
0,
)
);
if ptr as usize == usize::MAX {
// ptr = -1
roc_error_macros::internal_error!("failed to mmap shared pointer")
}
// fill the buffer with a fill pattern
libc::memset(ptr, 0xAA, Self::SHM_SIZE);
ptr
};
// puts in the initial header
let _ = ExpectSequence::new(ptr as *mut u8);
Self {
ptr: ptr.cast(),
length: Self::SHM_SIZE,
@ -83,7 +103,34 @@ impl<'a> ExpectMemory<'a> {
}
#[allow(clippy::too_many_arguments)]
pub fn run_expects<'a, W: std::io::Write>(
pub fn run_inline_expects<'a, W: std::io::Write>(
writer: &mut W,
render_target: RenderTarget,
arena: &'a Bump,
interns: &'a Interns,
layout_interner: &Arc<GlobalInterner<'a, Layout<'a>>>,
lib: &libloading::Library,
expectations: &mut VecMap<ModuleId, Expectations>,
expects: ExpectFunctions<'_>,
) -> std::io::Result<(usize, usize)> {
let shm_name = format!("/roc_expect_buffer_{}", std::process::id());
let mut memory = ExpectMemory::create_or_reuse_mmap(&shm_name);
run_expects_with_memory(
writer,
render_target,
arena,
interns,
layout_interner,
lib,
expectations,
expects,
&mut memory,
)
}
#[allow(clippy::too_many_arguments)]
pub fn run_toplevel_expects<'a, W: std::io::Write>(
writer: &mut W,
render_target: RenderTarget,
arena: &'a Bump,
@ -325,14 +372,16 @@ fn run_expect_fx<'a, W: std::io::Write>(
}
}
pub fn roc_dev_expect<'a>(
pub fn render_expects_in_memory<'a>(
writer: &mut impl std::io::Write,
arena: &'a Bump,
expectations: &mut VecMap<ModuleId, Expectations>,
interns: &'a Interns,
layout_interner: &Arc<GlobalInterner<'a, Layout<'a>>>,
shared_ptr: *mut u8,
memory: &ExpectMemory,
) -> std::io::Result<usize> {
let shared_ptr = memory.ptr;
let frame = ExpectFrame::at_offset(shared_ptr, ExpectSequence::START_OFFSET);
let module_id = frame.module_id;
@ -423,8 +472,7 @@ fn render_expect_failure<'a>(
start,
frame.start_offset,
&variables,
)
.unwrap();
);
renderer.render_failure(
writer,

View file

@ -17,6 +17,7 @@ indoc = "1.0.7"
strip-ansi-escapes = "0.1.1"
wasmer-wasi = "2.2.1"
roc_build = { path = "../compiler/build" }
roc_repl_cli = {path = "../repl_cli"}
roc_test_utils = {path = "../test_utils"}
@ -28,4 +29,18 @@ wasmer = { version = "2.2.1", default-features = false, features = ["singlepass"
wasmer = { version = "2.2.1", default-features = false, features = ["cranelift", "universal"] }
[features]
wasm = []
default = ["target-aarch64", "target-x86_64", "target-wasm32"]
wasm = ["target-wasm32"]
target-arm = ["roc_build/target-arm", "roc_repl_cli/target-arm"]
target-aarch64 = ["roc_build/target-aarch64", "roc_repl_cli/target-aarch64"]
target-x86 = ["roc_build/target-x86", "roc_repl_cli/target-x86"]
target-x86_64 = ["roc_build/target-x86_64", "roc_repl_cli/target-x86_64"]
target-wasm32 = ["roc_build/target-wasm32", "roc_repl_cli/target-wasm32"]
target-all = [
"target-aarch64",
"target-arm",
"target-x86",
"target-x86_64",
"target-wasm32"
]

View file

@ -3,16 +3,16 @@ use std::io::Write;
use std::path::PathBuf;
use std::process::{Command, ExitStatus, Stdio};
use roc_repl_cli::{TIPS, WELCOME_MESSAGE};
use roc_repl_cli::{SHORT_INSTRUCTIONS, WELCOME_MESSAGE};
use roc_test_utils::assert_multiline_str_eq;
const ERROR_MESSAGE_START: char = '─';
#[derive(Debug)]
struct Out {
stdout: String,
stderr: String,
status: ExitStatus,
pub struct Out {
pub stdout: String,
pub stderr: String,
pub status: ExitStatus,
}
fn path_to_roc_binary() -> PathBuf {
@ -39,7 +39,7 @@ fn path_to_roc_binary() -> PathBuf {
path
}
fn repl_eval(input: &str) -> Out {
pub fn repl_eval(input: &str) -> Out {
let mut cmd = Command::new(path_to_roc_binary());
cmd.arg("repl");
@ -75,7 +75,7 @@ fn repl_eval(input: &str) -> Out {
// Remove the initial instructions from the output.
let expected_instructions = format!("{}{}", WELCOME_MESSAGE, TIPS);
let expected_instructions = format!("{}{}", WELCOME_MESSAGE, SHORT_INSTRUCTIONS);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
@ -124,10 +124,25 @@ fn repl_eval(input: &str) -> Out {
}
pub fn expect_success(input: &str, expected: &str) {
let out = repl_eval(input);
let out = repl_eval(input.trim());
assert_multiline_str_eq!("", out.stderr.as_str());
assert_multiline_str_eq!(expected, out.stdout.as_str());
// Don't consider the auto variable name (e.g. "# val1") at the end.
// The state.rs tests do that!
let mut iter = out.stdout.lines().rev();
let line = iter.next().unwrap();
let comment_index = line.rfind('#').unwrap_or(line.len());
let line_without_comment = line[0..comment_index].trim_end();
// Sometimes the "# val1" wraps around to its own line; if this happens,
// we just use the preceding line instead.
if line_without_comment.is_empty() {
assert_multiline_str_eq!(expected, iter.next().unwrap().trim_end());
} else {
assert_multiline_str_eq!(expected, line_without_comment);
}
assert!(out.status.success());
}

View file

@ -6,6 +6,9 @@ extern crate lazy_static;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod state;
#[cfg(all(test, not(feature = "wasm")))]
mod cli;

View 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);
}

View file

@ -1,8 +1,9 @@
#[allow(unused_imports)]
use indoc::indoc;
use roc_test_utils::assert_multiline_str_eq;
#[cfg(not(feature = "wasm"))]
use crate::cli::{expect_failure, expect_success};
use crate::cli::{expect_failure, expect_success, repl_eval};
#[cfg(feature = "wasm")]
#[allow(unused_imports)]
@ -199,8 +200,7 @@ fn newtype_of_big_data() {
Either a b : [Left a, Right b]
lefty : Either Str Str
lefty = Left "loosey"
A lefty
"#
A lefty"#
),
r#"A (Left "loosey") : [A (Either Str Str)]"#,
)
@ -214,8 +214,7 @@ fn newtype_nested() {
Either a b : [Left a, Right b]
lefty : Either Str Str
lefty = Left "loosey"
A (B (C lefty))
"#
A (B (C lefty))"#
),
r#"A (B (C (Left "loosey"))) : [A [B [C (Either Str Str)]]]"#,
)
@ -229,8 +228,7 @@ fn newtype_of_big_of_newtype() {
Big a : [Big a [Wrapper [Newtype a]]]
big : Big Str
big = Big "s" (Wrapper (Newtype "t"))
A big
"#
A big"#
),
r#"A (Big "s" (Wrapper (Newtype "t"))) : [A (Big Str)]"#,
)
@ -560,8 +558,36 @@ fn four_element_record() {
);
}
#[cfg(not(feature = "wasm"))]
#[test]
fn multiline_string() {
fn multiline_string_non_wasm() {
// If a string contains newlines, format it as a multiline string in the output.
// We can't use expect_success to test this, because it only looks at the last
// line of output, and in this case we care about every line of output!
let out = repl_eval(r#""\n\nhi!\n\n""#);
let expected = indoc!(
r#""""
hi!
""" : Str"#
);
assert_multiline_str_eq!("", out.stderr.as_str());
// Don't consider the auto variable name ("# val1") at the end.
// The state.rs tests do that!
assert_multiline_str_eq!(expected, out.stdout.replace("# val1", "").trim());
assert!(out.status.success());
}
#[cfg(feature = "wasm")]
#[test]
fn multiline_string_wasm() {
// If a string contains newlines, format it as a multiline string in the output
expect_success(
r#""\n\nhi!\n\n""#,
@ -933,39 +959,7 @@ fn parse_problem() {
);
}
#[cfg(not(feature = "wasm"))] // TODO: mismatch is due to terminal control codes!
#[test]
fn exhaustiveness_problem() {
expect_failure(
indoc!(
r#"
t : [A, B, C]
t = A
when t is
A -> "a"
"#
),
indoc!(
r#"
UNSAFE PATTERN
This when does not cover all the possibilities:
7> when t is
8> A -> "a"
Other possibilities include:
B
C
I would have to crash if I saw one of those! Add branches for them!
"#
),
);
}
#[ignore] // re-enable (and fix) after https://github.com/roc-lang/roc/issues/4425 is done!
#[cfg(not(feature = "wasm"))]
#[test]
fn issue_2343_complete_mono_with_shadowed_vars() {
@ -1024,8 +1018,7 @@ fn tag_with_type_behind_alias() {
T : [A Str]
v : T
v = A "value"
v
"#
v"#
),
r#"A "value" : T"#,
);
@ -1039,8 +1032,7 @@ fn issue_2588_record_with_function_and_nonfunction() {
r#"
x = 1
f = \n -> n * 2
{ y: f x, f }
"#
{ y: f x, f }"#
),
r#"{ f: <function>, y: 2 } : { f : Num a -> Num a, y : Num * }"#,
)
@ -1053,8 +1045,7 @@ fn opaque_apply() {
r#"
Age := U32
@Age 23
"#
@Age 23"#
),
"@Age 23 : Age",
)
@ -1067,8 +1058,7 @@ fn opaque_apply_polymorphic() {
r#"
F t u := [Package t u]
@F (Package "" { a: "" })
"#
@F (Package "" { a: "" })"#
),
r#"@F (Package "" { a: "" }) : F Str { a : Str }"#,
)
@ -1083,8 +1073,7 @@ fn opaque_pattern_and_call() {
f = \@F (Package A {}) -> @F (Package {} A)
f (@F (Package A {}))
"#
f (@F (Package A {}))"#
),
r#"@F (Package {} A) : F {} [A]"#,
)
@ -1097,10 +1086,9 @@ fn dec_in_repl() {
r#"
x: Dec
x=1.23
x
"#
x"#
),
r#"1.23 : Dec"#,
"1.23 : Dec",
)
}
@ -1111,8 +1099,7 @@ fn print_i8_issue_2710() {
r#"
a : I8
a = -1
a
"#
a"#
),
r#"-1 : I8"#,
)
@ -1124,8 +1111,7 @@ fn box_box() {
expect_success(
indoc!(
r#"
Box.box "container store"
"#
Box.box "container store""#
),
r#"Box.box "container store" : Box Str"#,
)
@ -1140,8 +1126,7 @@ fn box_box_type_alias() {
HeapStr : Box Str
helloHeap : HeapStr
helloHeap = Box.box "bye stacks"
helloHeap
"#
helloHeap"#
),
r#"Box.box "bye stacks" : HeapStr"#,
)
@ -1165,9 +1150,7 @@ fn issue_2818() {
f : {} -> List Str
f = \_ ->
x = []
x
f
"#
x"#
),
r"<function> : {} -> List Str",
)
@ -1186,8 +1169,7 @@ fn issue_2810_recursive_layout_inside_nonrecursive() {
a : Job
a = Job (Command (FromJob (Job (Command SystemTool))))
a
"#
a"#
),
"Job (Command (FromJob (Job (Command SystemTool)))) : Job",
)
@ -1199,14 +1181,10 @@ fn render_nullable_unwrapped_passing_through_alias() {
indoc!(
r#"
Deep : [L DeepList]
DeepList : [Nil, Cons Deep]
v : DeepList
v = (Cons (L (Cons (L (Cons (L Nil))))))
v
"#
v"#
),
"Cons (L (Cons (L (Cons (L Nil))))) : DeepList",
)
@ -1218,8 +1196,7 @@ fn opaque_wrap_function() {
indoc!(
r#"
A a := a
List.map [1u8, 2u8, 3u8] @A
"#
List.map [1u8, 2u8, 3u8] @A"#
),
"[@A 1, @A 2, @A 3] : List (A U8)",
);
@ -1230,8 +1207,7 @@ fn dict_get_single() {
expect_success(
indoc!(
r#"
Dict.single 0 {a: 1, c: 2} |> Dict.get 0
"#
Dict.single 0 {a: 1, c: 2} |> Dict.get 0"#
),
r#"Ok { a: 1, c: 2 } : Result { a : Num *, c : Num * } [KeyNotFound]"#,
)
@ -1242,8 +1218,7 @@ fn record_of_poly_function() {
expect_success(
indoc!(
r#"
{ a: \_ -> "a" }
"#
{ a: \_ -> "a" }"#
),
r#"{ a: <function> } : { a : * -> Str }"#,
);
@ -1254,8 +1229,7 @@ fn record_of_poly_function_and_string() {
expect_success(
indoc!(
r#"
{ a: \_ -> "a", b: "b" }
"#
{ a: \_ -> "a", b: "b" }"#
),
r#"{ a: <function>, b: "b" } : { a : * -> Str, b : Str }"#,
);
@ -1266,8 +1240,7 @@ fn newtype_by_void_is_wrapped() {
expect_success(
indoc!(
r#"
Result.try (Err 42) (\x -> Err (x+1))
"#
Result.try (Err 42) (\x -> Err (x+1))"#
),
r#"Err 42 : Result b (Num *)"#,
);
@ -1275,8 +1248,7 @@ fn newtype_by_void_is_wrapped() {
expect_success(
indoc!(
r#"
Result.try (Ok 42) (\x -> Ok (x+1))
"#
Result.try (Ok 42) (\x -> Ok (x+1))"#
),
r#"Ok 43 : Result (Num *) err"#,
);

View file

@ -7,7 +7,7 @@ use roc_load::MonomorphizedModule;
use roc_parse::ast::Expr;
use roc_repl_eval::{
eval::jit_to_ast,
gen::{compile_to_mono, format_answer, ReplOutput},
gen::{compile_to_mono, format_answer},
ReplApp, ReplAppMemory,
};
use roc_reporting::report::DEFAULT_PALETTE_HTML;
@ -175,9 +175,31 @@ pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
// Compile the app
let target_info = TargetInfo::default_wasm32();
let mono = match compile_to_mono(arena, &src, target_info, DEFAULT_PALETTE_HTML) {
Ok(m) => m,
Err(messages) => return Err(messages.join("\n\n")),
// TODO use this to filter out problems and warnings in wrapped defs.
// See the variable by the same name in the CLI REPL for how to do this!
let mono = match compile_to_mono(
arena,
std::iter::empty(),
&src,
target_info,
DEFAULT_PALETTE_HTML,
) {
(Some(m), problems) if problems.is_empty() => m, // TODO render problems and continue if possible
(_, problems) => {
// TODO always report these, but continue if possible with the MonomorphizedModule if we have one.
let mut buf = String::new();
// Join all the errors and warnings together with blank lines.
for message in problems.errors.iter().chain(problems.warnings.iter()) {
if !buf.is_empty() {
buf.push_str("\n\n");
}
buf.push_str(message);
}
return Err(buf);
}
};
let MonomorphizedModule {
@ -270,10 +292,11 @@ pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
target_info,
);
let var_name = String::new(); // TODO turn this into something like " # val1"
// Transform the Expr to a string
// `Result::Err` becomes a JS exception that will be caught and displayed
match format_answer(arena, res_answer, expr_type_str) {
ReplOutput::NoProblems { expr, expr_type } => Ok(format!("{} : {}", expr, expr_type)),
ReplOutput::Problems(lines) => Err(format!("\n{}\n", lines.join("\n\n"))),
}
let expr = format_answer(arena, res_answer);
Ok(format!("{expr} : {expr_type_str}{var_name}"))
}

View file

@ -73,6 +73,41 @@ pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
}
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_getppid() -> libc::pid_t {
libc::getppid()
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_mmap(
addr: *mut libc::c_void,
len: libc::size_t,
prot: libc::c_int,
flags: libc::c_int,
fd: libc::c_int,
offset: libc::off_t,
) -> *mut libc::c_void {
libc::mmap(addr, len, prot, flags, fd, offset)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_shm_open(
name: *const libc::c_char,
oflag: libc::c_int,
mode: libc::mode_t,
) -> libc::c_int {
libc::shm_open(name, oflag, mode as libc::c_uint)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_send_signal(pid: libc::pid_t, sig: libc::c_int) -> libc::c_int {
libc::kill(pid, sig)
}
fn print_backtrace() {
eprintln!("Here is the call stack that led to the crash:\n");
@ -263,7 +298,7 @@ pub extern "C" fn roc_fx_exePath(roc_str: &RocStr) -> RocResult<RocList<u8>, ()>
#[no_mangle]
pub extern "C" fn roc_fx_stdinLine() -> RocStr {
use std::io::{self, BufRead};
use std::io::BufRead;
let stdin = io::stdin();
let line1 = stdin.lock().lines().next().unwrap().unwrap();

View file

@ -1,6 +1,6 @@
const std = @import("std");
const str = @import("str");
const builtin = @import("builtin");
const str = @import("str");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
@ -85,6 +85,34 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size);
}
extern fn kill(pid: c_int, sig: c_int) c_int;
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
extern fn getppid() c_int;
fn roc_getppid() callconv(.C) c_int {
return getppid();
}
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
return kill(pid, sig);
}
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
return shm_open(name, oflag, mode);
}
fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
return mmap(addr, length, prot, flags, fd, offset);
}
comptime {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
}
}
const Unit = extern struct {};
pub export fn main() u8 {

View file

@ -2,7 +2,7 @@
use core::alloc::Layout;
use core::ffi::c_void;
use core::mem::{ManuallyDrop, MaybeUninit};
use core::mem::MaybeUninit;
use libc;
use roc_std::{RocList, RocStr};
use std::env;
@ -72,6 +72,41 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut
libc::memset(dst, c, n)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_getppid() -> libc::pid_t {
libc::getppid()
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_mmap(
addr: *mut libc::c_void,
len: libc::size_t,
prot: libc::c_int,
flags: libc::c_int,
fd: libc::c_int,
offset: libc::off_t,
) -> *mut libc::c_void {
libc::mmap(addr, len, prot, flags, fd, offset)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_shm_open(
name: *const libc::c_char,
oflag: libc::c_int,
mode: libc::mode_t,
) -> libc::c_int {
libc::shm_open(name, oflag, mode as libc::c_uint)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_send_signal(pid: libc::pid_t, sig: libc::c_int) -> libc::c_int {
libc::kill(pid, sig)
}
#[no_mangle]
pub extern "C" fn rust_main() -> i32 {
let arg = env::args()
@ -122,9 +157,7 @@ unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 {
#[no_mangle]
pub extern "C" fn roc_fx_getLine() -> RocStr {
use std::io::{self, BufRead};
let stdin = io::stdin();
let stdin = std::io::stdin();
let line1 = stdin.lock().lines().next().unwrap().unwrap();
RocStr::from(line1.as_str())
@ -132,11 +165,10 @@ pub extern "C" fn roc_fx_getLine() -> RocStr {
#[no_mangle]
pub extern "C" fn roc_fx_getChar() -> u8 {
use std::io::{self, BufRead};
let mut buffer = [0];
if let Err(ioerr) = io::stdin().lock().read_exact(&mut buffer[..]) {
if ioerr.kind() == io::ErrorKind::UnexpectedEof {
if let Err(ioerr) = std::io::stdin().lock().read_exact(&mut buffer[..]) {
if ioerr.kind() == std::io::ErrorKind::UnexpectedEof {
u8::MAX
} else {
panic!("Got an unexpected error while reading char from stdin");

View file

@ -1,6 +1,6 @@
const std = @import("std");
const str = @import("str");
const builtin = @import("builtin");
const str = @import("str");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
@ -144,6 +144,34 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size);
}
extern fn kill(pid: c_int, sig: c_int) c_int;
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
extern fn getppid() c_int;
fn roc_getppid() callconv(.C) c_int {
return getppid();
}
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
return kill(pid, sig);
}
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
return shm_open(name, oflag, mode);
}
fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
return mmap(addr, length, prot, flags, fd, offset);
}
comptime {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
}
}
const Unit = extern struct {};
pub export fn main() callconv(.C) u8 {

View file

@ -140,14 +140,14 @@ string : Str -> Parser RawStr Str
string = \expectedString ->
strToRaw expectedString
|> stringRaw
|> map (\_val -> expectedString)
|> map \_val -> expectedString
scalar : U32 -> Parser RawStr U32
scalar = \expectedScalar ->
expectedScalar
|> strFromScalar
|> string
|> map (\_ -> expectedScalar)
|> map \_ -> expectedScalar
# Matches any codeunit
anyCodeunit : Parser RawStr U8
@ -181,7 +181,7 @@ digit =
|> List.map \digitNum ->
digitNum
|> codeunit
|> map (\_ -> digitNum)
|> map \_ -> digitNum
oneOf digitParsers
@ -192,7 +192,7 @@ digits =
|> map \digitsList ->
digitsList
|> List.map Num.intCast
|> List.walk 0 (\sum, digitVal -> 10 * sum + digitVal)
|> List.walk 0 \sum, digitVal -> 10 * sum + digitVal
## Try a bunch of different parsers.
##

View file

@ -95,6 +95,11 @@ void *roc_memset(void *str, int c, size_t n) {
return memset(str, c, n);
}
int roc_send_signal(int pid, int sig) { return kill(pid, sig); }
int roc_shm_open(char* name, int oflag, int mode) { return shm_open(name, oflag, mode); }
void* roc_mmap(void* addr, int length, int prot, int flags, int fd, int offset) { return mmap(addr, length, prot, flags, fd, offset); }
int roc_getppid() { return getppid(); }
struct RocStr {
char *bytes;
size_t len;

View file

@ -27,6 +27,12 @@ void* roc_memcpy(void* dest, const void* src, size_t n) {
void* roc_memset(void* str, int c, size_t n) { return memset(str, c, n); }
int roc_send_signal(int pid, int sig) { return kill(pid, sig); }
int roc_shm_open(char* name, int oflag, int mode) { return shm_open(name, oflag, mode); }
void* roc_mmap(void* addr, int length, int prot, int flags, int fd, int offset) { return mmap(addr, length, prot, flags, fd, offset); }
int roc_getppid() { return getppid(); }
struct RocStr {
char* bytes;
size_t len;

View file

@ -54,6 +54,41 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut
libc::memset(dst, c, n)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_getppid() -> libc::pid_t {
libc::getppid()
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_mmap(
addr: *mut libc::c_void,
len: libc::size_t,
prot: libc::c_int,
flags: libc::c_int,
fd: libc::c_int,
offset: libc::off_t,
) -> *mut libc::c_void {
libc::mmap(addr, len, prot, flags, fd, offset)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_shm_open(
name: *const libc::c_char,
oflag: libc::c_int,
mode: libc::mode_t,
) -> libc::c_int {
libc::shm_open(name, oflag, mode as libc::c_uint)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_send_signal(pid: libc::pid_t, sig: libc::c_int) -> libc::c_int {
libc::kill(pid, sig)
}
#[no_mangle]
pub extern "C" fn rust_main() -> i32 {
let mut roc_str = RocStr::default();

View file

@ -75,6 +75,34 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size);
}
extern fn kill(pid: c_int, sig: c_int) c_int;
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
extern fn getppid() c_int;
fn roc_getppid() callconv(.C) c_int {
return getppid();
}
fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
return kill(pid, sig);
}
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
return shm_open(name, oflag, mode);
}
fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
return mmap(addr, length, prot, flags, fd, offset);
}
comptime {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
}
}
const mem = std.mem;
const Allocator = mem.Allocator;

View file

@ -27,6 +27,11 @@ void* roc_memcpy(void* dest, const void* src, size_t n) {
void* roc_memset(void* str, int c, size_t n) { return memset(str, c, n); }
int roc_send_signal(int pid, int sig) { return kill(pid, sig); }
int roc_shm_open(char* name, int oflag, int mode) { return shm_open(name, oflag, mode); }
void* roc_mmap(void* addr, int length, int prot, int flags, int fd, int offset) { return mmap(addr, length, prot, flags, fd, offset); }
int roc_getppid() { return getppid(); }
struct RocStr {
char* bytes;
size_t len;

View file

@ -33,6 +33,41 @@ pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
libc::free(c_ptr)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_getppid() -> libc::pid_t {
libc::getppid()
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_mmap(
addr: *mut libc::c_void,
len: libc::size_t,
prot: libc::c_int,
flags: libc::c_int,
fd: libc::c_int,
offset: libc::off_t,
) -> *mut libc::c_void {
libc::mmap(addr, len, prot, flags, fd, offset)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_shm_open(
name: *const libc::c_char,
oflag: libc::c_int,
mode: libc::mode_t,
) -> libc::c_int {
libc::shm_open(name, oflag, mode as libc::c_uint)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_send_signal(pid: libc::pid_t, sig: libc::c_int) -> libc::c_int {
libc::kill(pid, sig)
}
#[no_mangle]
pub extern "C" fn rust_main() -> i32 {
let args: Vec<String> = env::args().collect();