Merge branch 'trunk' into nix-pure

This commit is contained in:
Richard Feldman 2021-03-15 21:07:48 -04:00 committed by GitHub
commit 3ab771cfaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
200 changed files with 32531 additions and 18690 deletions

View File

@ -15,13 +15,10 @@ jobs:
steps:
- uses: actions/checkout@v2
with:
clean: 'false'
clean: "false"
- name: Earthly version
run: earthly --version
- name: Make empty cache folder if it does exist
run: mkdir -p sccache_dir;
- name: install dependencies, build, run zig tests, rustfmt, clippy, cargo test --release
- name: install dependencies, build, run zig tests, rustfmt, clippy, cargo test --release
run: earthly +test-all

1555
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,4 @@
[workspace]
members = [
"compiler/region",
"compiler/collections",
@ -20,6 +19,7 @@ members = [
"compiler/gen_dev",
"compiler/build",
"compiler/arena_pool",
"compiler/test_gen",
"vendor/ena",
"vendor/pathfinding",
"vendor/pretty",
@ -31,8 +31,10 @@ members = [
# Optimizations based on https://deterministic.space/high-performance-rust.html
[profile.release]
lto = "fat"
lto = "thin"
codegen-units = 1
# debug = true # enable when profiling
[profile.bench]
lto = "thin"
codegen-units = 1

View File

@ -1,4 +1,4 @@
FROM rust:1.49-slim-buster
FROM rust:1.50-slim-buster
WORKDIR /earthbuild
prep-debian:
@ -8,7 +8,7 @@ install-other-libs:
FROM +prep-debian
RUN apt -y install wget git
RUN apt -y install libxcb-shape0-dev libxcb-xfixes0-dev # for editor clipboard
RUN apt -y install libc++-dev libc++abi-dev libunwind-dev pkg-config libx11-dev zlib1g-dev
RUN apt -y install libc++-dev libc++abi-dev g++ libunwind-dev pkg-config libx11-dev zlib1g-dev
install-zig-llvm-valgrind-clippy-rustfmt:
FROM +install-other-libs
@ -24,74 +24,88 @@ install-zig-llvm-valgrind-clippy-rustfmt:
RUN ln -s /usr/bin/clang-10 /usr/bin/clang
# use lld as linker
RUN ln -s /usr/bin/lld-10 /usr/bin/ld.lld
RUN echo "[build]" > $CARGO_HOME/config.toml
RUN echo "rustflags = [\"-C\", \"link-arg=-fuse-ld=lld\", \"-C\", \"target-cpu=native\"]" >> $CARGO_HOME/config.tom
ENV RUSTFLAGS="-C link-arg=-fuse-ld=lld -C target-cpu=native"
# valgrind
RUN apt -y install autotools-dev cmake automake libc6-dbg
RUN wget https://sourceware.org/pub/valgrind/valgrind-3.16.1.tar.bz2
RUN tar -xf valgrind-3.16.1.tar.bz2
# need to cd every time, every command starts at WORKDIR
RUN cd valgrind-3.16.1; ./autogen.sh
RUN cd valgrind-3.16.1; ./configure --disable-dependency-tracking
RUN cd valgrind-3.16.1; make -j`nproc`
RUN cd valgrind-3.16.1; make install
RUN cd valgrind-3.16.1 && ./autogen.sh
RUN cd valgrind-3.16.1 && ./configure --disable-dependency-tracking
RUN cd valgrind-3.16.1 && make -j`nproc`
RUN cd valgrind-3.16.1 && make install
# clippy
RUN rustup component add clippy
# rustfmt
RUN rustup component add rustfmt
# sccache
RUN apt install libssl-dev
RUN apt -y install libssl-dev
RUN cargo install sccache
RUN sccache -V
ENV RUSTC_WRAPPER=/usr/local/cargo/bin/sccache
ENV SCCACHE_DIR=/earthbuild/sccache_dir
ENV CARGO_INCREMENTAL=0 # no need to recompile package when using new function
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo install cargo-chef
deps-image:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
SAVE IMAGE roc-deps:latest
copy-dirs:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
# If you edit this, make sure to update copy-dirs-and-cache below.
COPY --dir cli compiler docs editor roc_std vendor examples Cargo.toml Cargo.lock ./
copy-dirs-and-cache:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
# cache
COPY --dir sccache_dir ./
# roc dirs
COPY +save-cache/target ./target
COPY +save-cache/cargo_home $CARGO_HOME
# This needs to be kept in sync with copy-dirs above.
# The reason this is at the end is to maximize caching.
# Lines above this should be cached even if the code changes.
COPY --dir cli compiler docs editor roc_std vendor examples Cargo.toml Cargo.lock ./
prepare-cache:
FROM +copy-dirs
RUN cargo chef prepare
SAVE ARTIFACT recipe.json
save-cache:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY +prepare-cache/recipe.json ./
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo chef cook && sccache --show-stats # for clippy
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo chef cook --release --tests && sccache --show-stats
SAVE ARTIFACT target
SAVE ARTIFACT $CARGO_HOME cargo_home
test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir compiler/builtins/bitcode ./
RUN cd bitcode; ./run-tests.sh;
build-rust:
FROM +copy-dirs-and-cache
ARG RUSTC_WRAPPER=/usr/local/cargo/bin/sccache
ARG SCCACHE_DIR=/earthbuild/sccache_dir
ARG CARGO_INCREMENTAL=0 # no need to recompile package when using new function
RUN cargo build; sccache --show-stats # for clippy
RUN cargo test --release --no-run; sccache --show-stats
RUN cd bitcode && ./run-tests.sh
check-clippy:
FROM +build-rust
FROM +copy-dirs-and-cache
RUN cargo clippy -V
RUN cargo clippy -- -D warnings
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo clippy -- -D warnings
check-rustfmt:
FROM +copy-dirs-and-cache
FROM +copy-dirs
RUN cargo fmt --version
RUN cargo fmt --all -- --check
save-cache:
FROM +build-rust
SAVE ARTIFACT sccache_dir AS LOCAL sccache_dir
test-rust:
FROM +build-rust
ARG RUSTC_WRAPPER=/usr/local/cargo/bin/sccache
ARG SCCACHE_DIR=/earthbuild/sccache_dir
RUN cargo test --release
FROM +copy-dirs-and-cache
ENV RUST_BACKTRACE=1
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --release && sccache --show-stats
test-all:
BUILD +check-clippy
BUILD +check-rustfmt
BUILD +save-cache
BUILD +test-zig
BUILD +check-rustfmt
BUILD +check-clippy
BUILD +test-rust

View File

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2019 Richard Feldman
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -52,7 +52,7 @@ By using systems-level programming languages like C and C++, platform authors sa
Roc is designed to make the "systems-level platform, higher-level application" experience as nice as possible.
* **Application** authors code exclusively in Roc. It's a language designed for nice ergonomics. The syntax resembles Ruby or CoffeeScript, and it has a fast compiler with full type inference.
* **Platform** authors code almost exclusively in a systems-level langauge like C, C++, Rust, or [Zig](https://ziglang.org/), except for the thin Roc API they expose to application authors. Roc application code compiles to machine code, and production builds of Roc apps benefit from the same [LLLM](https://llvm.org/) optimizations that C++, Rust, and Zig do. Roc application authors do not need to know this lower-level code exists; all they have to interact with is the platform's API, which is exposed as an ordinary Roc API.
* **Platform** authors code almost exclusively in a systems-level langauge like C, C++, Rust, or [Zig](https://ziglang.org/), except for the thin Roc API they expose to application authors. Roc application code compiles to machine code, and production builds of Roc apps benefit from the same [LLVM](https://llvm.org/) optimizations that C++, Rust, and Zig do. Roc application authors do not need to know this lower-level code exists; all they have to interact with is the platform's API, which is exposed as an ordinary Roc API.
Every Roc application is built on top of exactly one Roc platform. There is no such thing as a Roc application that runs without a platform, and there is no default platform. You must choose one!

View File

@ -58,7 +58,6 @@ im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", "process", "io-driver"] }
libc = "0.2"
libloading = "0.6"
@ -81,6 +80,7 @@ libloading = "0.6"
# This way, GitHub Actions works and nobody's builds get broken.
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release3" }
target-lexicon = "0.10"
tempfile = "3.1.0"
[dev-dependencies]
pretty_assertions = "0.5.1"

View File

@ -7,10 +7,11 @@ use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap;
use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem;
use std::fs;
use std::env;
use std::path::PathBuf;
use std::time::{Duration, SystemTime};
use target_lexicon::Triple;
use tempfile::Builder;
fn report_timing(buf: &mut String, label: &str, duration: Duration) {
buf.push_str(&format!(
@ -52,8 +53,14 @@ pub fn build_file<'a>(
)?;
let path_to_platform = loaded.platform_path.clone();
let app_o_file = roc_file_path.with_file_name("roc_app.o");
let app_o_file = Builder::new()
.prefix("roc_app")
.suffix(".o")
.tempfile()
.map_err(|err| {
todo!("TODO Gracefully handle tempfile creation error {:?}", err);
})?;
let app_o_file = app_o_file.path();
let buf = &mut String::with_capacity(1024);
let mut it = loaded.timings.iter().peekable();
@ -96,13 +103,12 @@ pub fn build_file<'a>(
}
}
let cwd = app_o_file.parent().unwrap();
let cwd = roc_file_path.parent().unwrap();
let binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows
let code_gen_timing = program::gen_from_mono_module(
&arena,
loaded,
roc_file_path,
&roc_file_path,
Triple::host(),
&app_o_file,
opt_level,
@ -117,25 +123,33 @@ pub fn build_file<'a>(
report_timing(buf, "Generate LLVM IR", code_gen_timing.code_gen);
report_timing(buf, "Emit .o file", code_gen_timing.emit_o_file);
println!(
"\n\nCompilation finished! Here's how long each module took to compile:\n\n{}",
buf
);
println!("\nSuccess! 🎉\n\n\t{}\n", app_o_file.display());
let compilation_end = compilation_start.elapsed().unwrap();
let size = std::fs::metadata(&app_o_file).unwrap().len();
let size = std::fs::metadata(&app_o_file)
.unwrap_or_else(|err| {
panic!(
"Could not open {:?} - which was supposed to have been generated. Error: {:?}",
app_o_file, err
);
})
.len();
println!(
"Finished compilation and code gen in {} ms\n\nProduced a app.o file of size {:?}\n",
compilation_end.as_millis(),
size,
);
if emit_debug_info {
println!(
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
buf
);
println!(
"Finished compilation and code gen in {} ms\n\nProduced a app.o file of size {:?}\n",
compilation_end.as_millis(),
size,
);
}
// Step 2: link the precompiled host and compiled app
let mut host_input_path = PathBuf::from(cwd);
host_input_path.push(&*path_to_platform);
host_input_path.push("host.o");
@ -144,10 +158,13 @@ pub fn build_file<'a>(
let rebuild_host_start = SystemTime::now();
rebuild_host(host_input_path.as_path());
let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
println!(
"Finished rebuilding the host in {} ms\n",
rebuild_host_end.as_millis()
);
if emit_debug_info {
println!(
"Finished rebuilding the host in {} ms\n",
rebuild_host_end.as_millis()
);
}
// TODO try to move as much of this linking as possible to the precompiled
// host, to minimize the amount of host-application linking required.
@ -156,7 +173,7 @@ pub fn build_file<'a>(
link(
target,
binary_path,
&[host_input_path.as_path().to_str().unwrap(), app_o_file.as_path().to_str().unwrap()],
&[host_input_path.as_path().to_str().unwrap(), app_o_file.to_str().unwrap()],
link_type
)
.map_err(|_| {
@ -168,18 +185,25 @@ pub fn build_file<'a>(
});
let link_end = link_start.elapsed().unwrap();
println!("Finished linking in {} ms\n", link_end.as_millis());
// Clean up the leftover .o file from the Roc, if possible.
// (If cleaning it up fails, that's fine. No need to take action.)
// TODO compile the app_o_file to a tmpdir, as an extra precaution.
let _ = fs::remove_file(app_o_file);
if emit_debug_info {
println!("Finished linking in {} ms\n", link_end.as_millis());
}
// If the cmd errored out, return the Err.
cmd_result?;
// If possible, report the generated executable name relative to the current dir.
let generated_filename = binary_path
.strip_prefix(env::current_dir().unwrap())
.unwrap_or(&binary_path);
let total_end = compilation_start.elapsed().unwrap();
println!("Finished entire process in {} ms\n", total_end.as_millis());
println!(
"🎉 Built {} in {} ms",
generated_filename.to_str().unwrap(),
total_end.as_millis()
);
Ok(binary_path)
}

View File

@ -135,6 +135,9 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io
Err(LoadingProblem::ParsingFailedReport(report)) => {
print!("{}", report);
}
Err(LoadingProblem::NoPlatform(report)) => {
print!("{}", report);
}
Err(other) => {
panic!("build_file failed with error:\n{:?}", other);
}

View File

@ -82,6 +82,14 @@ fn jit_to_ast_help<'a>(
content
)))
}
Layout::Builtin(Builtin::Int128) => {
Ok(run_jit_function!(
lib,
main_fn_name,
i128,
|num| num_to_ast(env, i128_to_ast(env.arena, num), content)
))
}
Layout::Builtin(Builtin::Float64) => {
Ok(run_jit_function!(lib, main_fn_name, f64, |num| num_to_ast(
env,
@ -852,6 +860,12 @@ fn i64_to_ast(arena: &Bump, num: i64) -> Expr<'_> {
Expr::Num(arena.alloc(format!("{}", num)))
}
/// This is centralized in case we want to format it differently later,
/// e.g. adding underscores for large numbers
fn i128_to_ast(arena: &Bump, num: i128) -> Expr<'_> {
Expr::Num(arena.alloc(format!("{}", num)))
}
/// This is centralized in case we want to format it differently later,
/// e.g. adding underscores for large numbers
fn f64_to_ast(arena: &Bump, num: f64) -> Expr<'_> {

View File

@ -13,10 +13,20 @@ mod helpers;
mod cli_run {
use crate::helpers::{
example_file, extract_valgrind_errors, fixture_file, run_cmd, run_roc, run_with_valgrind,
ValgrindError, ValgrindErrorXWhat,
};
use serial_test::serial;
use std::path::Path;
#[cfg(not(target_os = "macos"))]
const ALLOW_VALGRIND: bool = true;
// Disallow valgrind on macOS by default, because it reports a ton
// of false positives. For local development on macOS, feel free to
// change this to true!
#[cfg(target_os = "macos")]
const ALLOW_VALGRIND: bool = false;
fn check_output(
file: &Path,
executable_filename: &str,
@ -48,7 +58,7 @@ mod cli_run {
}
assert!(compile_out.status.success());
let out = if use_valgrind {
let out = if use_valgrind && ALLOW_VALGRIND {
let (valgrind_out, raw_xml) = run_with_valgrind(
stdin_str,
&[file.with_file_name(executable_filename).to_str().unwrap()],
@ -60,7 +70,24 @@ mod cli_run {
});
if !memory_errors.is_empty() {
panic!("{:?}", memory_errors);
for error in memory_errors {
let ValgrindError {
kind,
what: _,
xwhat,
} = error;
println!("Valgrind Error: {}\n", kind);
if let Some(ValgrindErrorXWhat {
text,
leakedbytes: _,
leakedblocks: _,
}) = xwhat
{
println!(" {}", text);
}
}
panic!("Valgrind reported memory errors");
}
} else {
let exit_code = match valgrind_out.status.code() {
@ -181,7 +208,7 @@ mod cli_run {
"deriv",
&[],
"1 count: 6\n2 count: 22\n",
false,
true,
);
}
@ -209,6 +236,42 @@ mod cli_run {
);
}
#[test]
#[serial(astar)]
fn run_astar_optimized_1() {
check_output(
&example_file("benchmarks", "TestAStar.roc"),
"test-astar",
&[],
"True\n",
false,
);
}
#[test]
#[serial(base64)]
fn base64() {
check_output(
&example_file("benchmarks", "TestBase64.roc"),
"test-base64",
&[],
"encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n",
true,
);
}
#[test]
#[serial(closure)]
fn closure() {
check_output(
&example_file("benchmarks", "Closure.roc"),
"closure",
&[],
"",
true,
);
}
// #[test]
// #[serial(effect)]
// fn run_effect_unoptimized() {

View File

@ -5,6 +5,21 @@ const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
comptime {
// This is a workaround for https://github.com/ziglang/zig/issues/8218
// which is only necessary on macOS.
//
// Once that issue is fixed, we can undo the changes in
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
// -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!
if (std.builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
}
const mem = std.mem;
const Allocator = mem.Allocator;

View File

@ -5,6 +5,20 @@ const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
comptime {
// This is a workaround for https://github.com/ziglang/zig/issues/8218
// which is only necessary on macOS.
//
// Once that issue is fixed, we can undo the changes in
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
// -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!
if (std.builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
}
const mem = std.mem;
const Allocator = mem.Allocator;

View File

@ -192,20 +192,20 @@ struct ValgrindDummyStruct {}
#[derive(Debug, Deserialize, Clone)]
pub struct ValgrindError {
kind: String,
pub kind: String,
#[serde(default)]
what: Option<String>,
pub what: Option<String>,
#[serde(default)]
xwhat: Option<ValgrindErrorXWhat>,
pub xwhat: Option<ValgrindErrorXWhat>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ValgrindErrorXWhat {
text: String,
pub text: String,
#[serde(default)]
leakedbytes: Option<isize>,
pub leakedbytes: Option<isize>,
#[serde(default)]
leakedblocks: Option<isize>,
pub leakedblocks: Option<isize>,
}
#[allow(dead_code)]

View File

@ -27,6 +27,7 @@ bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1.0"
libloading = "0.6"
tempfile = "3.1.0"
serde_json = "1.0"
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything.
#
# The reason for this fork is that the way Inkwell is designed, you have to use

View File

@ -55,6 +55,109 @@ fn find_zig_str_path() -> PathBuf {
panic!("cannot find `str.zig`")
}
#[cfg(not(target_os = "macos"))]
fn build_zig_host(
env_path: &str,
env_home: &str,
emit_bin: &str,
zig_host_src: &str,
zig_str_path: &str,
) -> Output {
Command::new("zig")
.env_clear()
.env("PATH", env_path)
.env("HOME", env_home)
.args(&[
"build-obj",
zig_host_src,
emit_bin,
"--pkg-begin",
"str",
zig_str_path,
"--pkg-end",
// include the zig runtime
"-fcompiler-rt",
// include libc
"--library",
"c",
])
.output()
.unwrap()
}
#[cfg(target_os = "macos")]
fn build_zig_host(
env_path: &str,
env_home: &str,
emit_bin: &str,
zig_host_src: &str,
zig_str_path: &str,
) -> Output {
use serde_json::Value;
// Run `zig env` to find the location of zig's std/ directory
let zig_env_output = Command::new("zig").args(&["env"]).output().unwrap();
let zig_env_json = if zig_env_output.status.success() {
std::str::from_utf8(&zig_env_output.stdout).unwrap_or_else(|utf8_err| {
panic!(
"`zig env` failed; its stderr output was invalid utf8 ({:?})",
utf8_err
);
})
} else {
match std::str::from_utf8(&zig_env_output.stderr) {
Ok(stderr) => panic!("`zig env` failed - stderr output was: {:?}", stderr),
Err(utf8_err) => panic!(
"`zig env` failed; its stderr output was invalid utf8 ({:?})",
utf8_err
),
}
};
let mut zig_compiler_rt_path = match serde_json::from_str(zig_env_json) {
Ok(Value::Object(map)) => match map.get("std_dir") {
Some(Value::String(std_dir)) => PathBuf::from(Path::new(std_dir)),
_ => {
panic!("Expected JSON containing a `std_dir` String field from `zig env`, but got: {:?}", zig_env_json);
}
},
_ => {
panic!(
"Expected JSON containing a `std_dir` field from `zig env`, but got: {:?}",
zig_env_json
);
}
};
zig_compiler_rt_path.push("special");
zig_compiler_rt_path.push("compiler_rt.zig");
Command::new("zig")
.env_clear()
.env("PATH", &env_path)
.env("HOME", &env_home)
.args(&[
"build-obj",
zig_host_src,
&emit_bin,
"--pkg-begin",
"str",
zig_str_path,
"--pkg-end",
// include the zig runtime
"--pkg-begin",
"compiler_rt",
zig_compiler_rt_path.to_str().unwrap(),
"--pkg-end",
// include libc
"--library",
"c",
])
.output()
.unwrap()
}
pub fn rebuild_host(host_input_path: &Path) {
let c_host_src = host_input_path.with_file_name("host.c");
let c_host_dest = host_input_path.with_file_name("c_host.o");
@ -79,28 +182,17 @@ pub fn rebuild_host(host_input_path: &Path) {
&zig_str_path
);
let output = Command::new("zig")
.env_clear()
.env("PATH", &env_path)
.env("HOME", &env_home)
.args(&[
"build-obj",
zig_host_src.to_str().unwrap(),
validate_output(
"host.zig",
"zig",
build_zig_host(
&env_path,
&env_home,
&emit_bin,
"--pkg-begin",
"str",
zig_host_src.to_str().unwrap(),
zig_str_path.to_str().unwrap(),
"--pkg-end",
// include the zig runtime
"-fcompiler-rt",
// include libc
"--library",
"c",
])
.output()
.unwrap();
validate_output("host.zig", "zig", output);
),
);
} else {
// Compile host.c
let output = Command::new("clang")
@ -282,6 +374,7 @@ fn link_linux(
.collect::<HashMap<String, String>>(),
)
.args(&[
"--gc-sections",
"--eh-frame-hdr",
"-arch",
arch_str(target),
@ -350,6 +443,11 @@ fn link_macos(
// Don't allow LD_ env vars to affect this
.env_clear()
.args(&[
// NOTE: we don't do --gc-sections on macOS because the default
// macOS linker doesn't support it, but it's a performance
// optimization, so if we ever switch to a different linker,
// we'd like to re-enable it on macOS!
// "--gc-sections",
link_type_arg,
"-arch",
target.architecture.to_string().as_str(),

View File

@ -23,7 +23,7 @@ pub struct CodeGenTiming {
pub fn gen_from_mono_module(
arena: &Bump,
mut loaded: MonomorphizedModule,
_file_path: PathBuf,
roc_file_path: &Path,
target: Triple,
app_o_file: &Path,
opt_level: OptLevel,
@ -36,7 +36,14 @@ pub fn gen_from_mono_module(
let code_gen_start = SystemTime::now();
for (home, (module_path, src)) in loaded.sources {
let src_lines: Vec<&str> = src.split('\n').collect();
let mut src_lines: Vec<&str> = Vec::new();
if let Some((_, header_src)) = loaded.header_sources.get(&home) {
src_lines.extend(header_src.split('\n'));
src_lines.extend(src.split('\n').skip(1));
} else {
src_lines.extend(src.split('\n'));
}
let palette = DEFAULT_PALETTE;
// Report parsing and canonicalization problems
@ -84,6 +91,13 @@ pub fn gen_from_mono_module(
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::module::Linkage;
let app_ll_file = {
let mut temp = PathBuf::from(roc_file_path);
temp.set_extension("ll");
temp
};
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let attr = context.create_enum_attribute(kind_id, 1);
@ -97,6 +111,10 @@ pub fn gen_from_mono_module(
if name.starts_with("roc_builtins.dict") || name.starts_with("dict.RocDict") {
function.add_attribute(AttributeLoc::Function, attr);
}
if name.starts_with("roc_builtins.list") || name.starts_with("list.RocList") {
function.add_attribute(AttributeLoc::Function, attr);
}
}
let builder = context.create_builder();
@ -154,11 +172,16 @@ pub fn gen_from_mono_module(
fpm.run_on(&fn_val);
} else {
fn_val.print_to_stderr();
// write the ll code to a file, so we can modify it
env.module.print_to_file(&app_ll_file).unwrap();
// env.module.print_to_stderr();
// NOTE: If this fails, uncomment the above println to debug.
panic!(
r"Non-main function {:?} failed LLVM verification. Uncomment the above println to debug!",
r"Non-main function {:?} failed LLVM verification. I wrote the full LLVM IR to {:?}",
fn_val.get_name(),
app_ll_file,
);
}
}
@ -172,7 +195,13 @@ pub fn gen_from_mono_module(
// Verify the module
if let Err(errors) = env.module.verify() {
panic!("😱 LLVM errors when defining module: {:?}", errors);
// write the ll code to a file, so we can modify it
env.module.print_to_file(&app_ll_file).unwrap();
panic!(
"😱 LLVM errors when defining module; I wrote the full LLVM IR to {:?}\n\n {:?}",
app_ll_file, errors,
);
}
// Uncomment this to see the module's optimized LLVM instruction output:
@ -186,13 +215,10 @@ pub fn gen_from_mono_module(
if emit_debug_info {
module.strip_debug_info();
let mut app_ll_file = std::path::PathBuf::from(app_o_file);
app_ll_file.set_extension("ll");
let mut app_ll_dbg_file = std::path::PathBuf::from(app_o_file);
let mut app_ll_dbg_file = PathBuf::from(roc_file_path);
app_ll_dbg_file.set_extension("dbg.ll");
let mut app_bc_file = std::path::PathBuf::from(app_o_file);
let mut app_bc_file = PathBuf::from(roc_file_path);
app_bc_file.set_extension("bc");
use std::process::Command;

View File

@ -10,8 +10,6 @@ roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_types = { path = "../types" }
roc_can = { path = "../can" }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"

View File

@ -2,3 +2,4 @@ zig-cache
src/zig-cache
builtins.ll
builtins.bc
builtins.o

View File

@ -20,17 +20,29 @@ pub fn build(b: *Builder) void {
const test_step = b.step("test", "Run tests");
test_step.dependOn(&main_tests.step);
// Lib
// LLVM IR
const obj_name = "builtins";
const llvm_obj = b.addObject(obj_name, main_path);
llvm_obj.setBuildMode(mode);
llvm_obj.linkSystemLibrary("c");
llvm_obj.strip = true;
llvm_obj.emit_llvm_ir = true;
llvm_obj.emit_bin = false;
llvm_obj.bundle_compiler_rt = true;
const ir = b.step("ir", "Build LLVM ir");
ir.dependOn(&llvm_obj.step);
// Object File
// TODO: figure out how to get this to emit symbols that are only scoped to linkage (global but hidden).
// Also, zig has -ffunction-sections, but I am not sure how to add it here.
// With both of those changes, unused zig functions will be cleaned up by the linker saving around 100k.
const obj = b.addObject(obj_name, main_path);
obj.setBuildMode(mode);
obj.linkSystemLibrary("c");
obj.setOutputDir(".");
obj.strip = true;
obj.emit_llvm_ir = true;
obj.emit_bin = false;
obj.bundle_compiler_rt = true;
const ir = b.step("ir", "Build LLVM ir");
ir.dependOn(&obj.step);
const obj_step = b.step("object", "Build object file for linking");
obj_step.dependOn(&obj.step);
b.default_step = ir;
removeInstallSteps(b);

View File

@ -5,9 +5,10 @@ const mem = std.mem;
const Allocator = mem.Allocator;
const assert = std.debug.assert;
const utils = @import("utils.zig");
const RocList = @import("list.zig").RocList;
const INITIAL_SEED = 0xc70f6907;
const REFCOUNT_ONE_ISIZE: comptime isize = std.math.minInt(isize);
const REFCOUNT_ONE: usize = @bitCast(usize, REFCOUNT_ONE_ISIZE);
const InPlace = packed enum(u8) {
InPlace,
@ -92,6 +93,23 @@ const Alignment = packed enum(u8) {
}
};
pub fn decref(
allocator: *Allocator,
alignment: Alignment,
bytes_or_null: ?[*]u8,
data_bytes: usize,
) void {
return utils.decref(allocator, alignment.toUsize(), bytes_or_null, data_bytes);
}
pub fn allocateWithRefcount(
allocator: *Allocator,
alignment: Alignment,
data_bytes: usize,
) [*]u8 {
return utils.allocateWithRefcount(allocator, alignment.toUsize(), data_bytes);
}
pub const RocDict = extern struct {
dict_bytes: ?[*]u8,
dict_entries_len: usize,
@ -211,7 +229,7 @@ pub const RocDict = extern struct {
// otherwise, check if the refcount is one
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, self.dict_bytes));
return (ptr - 1)[0] == REFCOUNT_ONE;
return (ptr - 1)[0] == utils.REFCOUNT_ONE;
}
pub fn capacity(self: RocDict) usize {
@ -228,8 +246,6 @@ pub const RocDict = extern struct {
}
// unfortunately, we have to clone
const in_place = InPlace.Clone;
var new_dict = RocDict.allocate(allocator, self.number_of_levels, self.dict_entries_len, alignment, key_width, value_width);
var old_bytes: [*]u8 = @ptrCast([*]u8, self.dict_bytes);
@ -260,6 +276,10 @@ pub const RocDict = extern struct {
}
fn setKey(self: *RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize, data: Opaque) void {
if (key_width == 0) {
return;
}
const offset = blk: {
if (alignment.keyFirst()) {
break :blk (index * key_width);
@ -276,6 +296,10 @@ pub const RocDict = extern struct {
}
fn getKey(self: *const RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize) Opaque {
if (key_width == 0) {
return null;
}
const offset = blk: {
if (alignment.keyFirst()) {
break :blk (index * key_width);
@ -289,6 +313,10 @@ pub const RocDict = extern struct {
}
fn setValue(self: *RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize, data: Opaque) void {
if (value_width == 0) {
return;
}
const offset = blk: {
if (alignment.keyFirst()) {
break :blk (self.capacity() * key_width) + (index * value_width);
@ -305,6 +333,10 @@ pub const RocDict = extern struct {
}
fn getValue(self: *const RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize) Opaque {
if (value_width == 0) {
return null;
}
const offset = blk: {
if (alignment.keyFirst()) {
break :blk (self.capacity() * key_width) + (index * value_width);
@ -468,6 +500,8 @@ pub fn dictRemove(input: RocDict, alignment: Alignment, key: Opaque, key_width:
if (dict.dict_entries_len == 0) {
const data_bytes = dict.capacity() * slotSize(key_width, value_width);
decref(std.heap.c_allocator, alignment, dict.dict_bytes, data_bytes);
output.* = RocDict.empty();
return;
}
output.* = dict;
@ -518,11 +552,6 @@ pub fn elementsRc(dict: RocDict, alignment: Alignment, key_width: usize, value_w
}
}
pub const RocList = extern struct {
bytes: ?[*]u8,
length: usize,
};
pub fn dictKeys(dict: RocDict, alignment: Alignment, key_width: usize, value_width: usize, inc_key: Inc, output: *RocList) callconv(.C) void {
const size = dict.capacity();
@ -538,7 +567,7 @@ pub fn dictKeys(dict: RocDict, alignment: Alignment, key_width: usize, value_wid
}
if (length == 0) {
output.* = RocList{ .bytes = null, .length = 0 };
output.* = RocList.empty();
return;
}
@ -587,7 +616,7 @@ pub fn dictValues(dict: RocDict, alignment: Alignment, key_width: usize, value_w
}
if (length == 0) {
output.* = RocList{ .bytes = null, .length = 0 };
output.* = RocList.empty();
return;
}
@ -621,82 +650,146 @@ pub fn dictValues(dict: RocDict, alignment: Alignment, key_width: usize, value_w
output.* = RocList{ .bytes = ptr, .length = length };
}
fn decref(
allocator: *Allocator,
alignment: Alignment,
bytes_or_null: ?[*]u8,
data_bytes: usize,
) void {
var bytes = bytes_or_null orelse return;
fn doNothing(ptr: Opaque) callconv(.C) void {
return;
}
const usizes: [*]usize = @ptrCast([*]usize, @alignCast(8, bytes));
pub fn dictUnion(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, inc_key: Inc, inc_value: Inc, output: *RocDict) callconv(.C) void {
output.* = dict1.makeUnique(std.heap.c_allocator, alignment, key_width, value_width);
const refcount = (usizes - 1)[0];
const refcount_isize = @bitCast(isize, refcount);
var i: usize = 0;
while (i < dict2.capacity()) : (i += 1) {
switch (dict2.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const key = dict2.getKey(i, alignment, key_width, value_width);
switch (alignment.toUsize()) {
8 => {
if (refcount == REFCOUNT_ONE) {
allocator.free((bytes - 8)[0 .. 8 + data_bytes]);
} else if (refcount_isize < 0) {
(usizes - 1)[0] = refcount + 1;
}
},
16 => {
if (refcount == REFCOUNT_ONE) {
allocator.free((bytes - 16)[0 .. 16 + data_bytes]);
} else if (refcount_isize < 0) {
(usizes - 1)[0] = refcount + 1;
}
},
else => unreachable,
switch (output.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
const value = dict2.getValue(i, alignment, key_width, value_width);
inc_value(value);
// we need an extra RC token for the key
inc_key(key);
inc_value(value);
// we know the newly added key is not a duplicate, so the `dec`s are unreachable
const dec_key = doNothing;
const dec_value = doNothing;
dictInsert(output.*, alignment, key, key_width, value, value_width, hash_fn, is_eq, dec_key, dec_value, output);
},
MaybeIndex.index => |_| {
// the key is already in the output dict
continue;
},
}
},
else => {},
}
}
}
fn allocateWithRefcount(
allocator: *Allocator,
alignment: Alignment,
data_bytes: usize,
) [*]u8 {
comptime const result_in_place = InPlace.Clone;
pub fn dictIntersection(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Inc, dec_value: Inc, output: *RocDict) callconv(.C) void {
output.* = dict1.makeUnique(std.heap.c_allocator, alignment, key_width, value_width);
switch (alignment.toUsize()) {
8 => {
const length = @sizeOf(usize) + data_bytes;
var i: usize = 0;
const size = dict1.capacity();
while (i < size) : (i += 1) {
switch (output.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const key = dict1.getKey(i, alignment, key_width, value_width);
var new_bytes: []align(8) u8 = allocator.alignedAlloc(u8, 8, length) catch unreachable;
var as_usize_array = @ptrCast([*]usize, new_bytes);
if (result_in_place == InPlace.InPlace) {
as_usize_array[0] = @intCast(usize, number_of_slots);
} else {
as_usize_array[0] = REFCOUNT_ONE;
}
var as_u8_array = @ptrCast([*]u8, new_bytes);
const first_slot = as_u8_array + @sizeOf(usize);
return first_slot;
},
16 => {
const length = 2 * @sizeOf(usize) + data_bytes;
var new_bytes: []align(16) u8 = allocator.alignedAlloc(u8, 16, length) catch unreachable;
var as_usize_array = @ptrCast([*]usize, new_bytes);
if (result_in_place == InPlace.InPlace) {
as_usize_array[0] = 0;
as_usize_array[1] = @intCast(usize, number_of_slots);
} else {
as_usize_array[0] = 0;
as_usize_array[1] = REFCOUNT_ONE;
}
var as_u8_array = @ptrCast([*]u8, new_bytes);
const first_slot = as_u8_array + 2 * @sizeOf(usize);
return first_slot;
},
else => unreachable,
switch (dict2.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
dictRemove(output.*, alignment, key, key_width, value_width, hash_fn, is_eq, dec_key, dec_value, output);
},
MaybeIndex.index => |_| {
// keep this key/value
continue;
},
}
},
else => {},
}
}
}
pub fn dictDifference(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, dec_value: Dec, output: *RocDict) callconv(.C) void {
output.* = dict1.makeUnique(std.heap.c_allocator, alignment, key_width, value_width);
var i: usize = 0;
const size = dict1.capacity();
while (i < size) : (i += 1) {
switch (output.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const key = dict1.getKey(i, alignment, key_width, value_width);
switch (dict2.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
// keep this key/value
continue;
},
MaybeIndex.index => |_| {
dictRemove(output.*, alignment, key, key_width, value_width, hash_fn, is_eq, dec_key, dec_value, output);
},
}
},
else => {},
}
}
}
pub fn setFromList(list: RocList, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, output: *RocDict) callconv(.C) void {
output.* = RocDict.empty();
var ptr = @ptrCast([*]u8, list.bytes);
const dec_value = doNothing;
const value = null;
const size = list.length;
var i: usize = 0;
while (i < size) : (i += 1) {
const key = ptr + i * key_width;
dictInsert(output.*, alignment, key, key_width, value, value_width, hash_fn, is_eq, dec_key, dec_value, output);
}
// NOTE: decref checks for the empty case
const data_bytes = size * key_width;
decref(std.heap.c_allocator, alignment, list.bytes, data_bytes);
}
const StepperCaller = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
pub fn dictWalk(dict: RocDict, stepper: Opaque, stepper_caller: StepperCaller, accum: Opaque, alignment: Alignment, key_width: usize, value_width: usize, accum_width: usize, inc_key: Inc, inc_value: Inc, output: Opaque) callconv(.C) void {
// allocate space to write the result of the stepper into
// experimentally aliasing the accum and output pointers is not a good idea
const alloc: [*]u8 = @ptrCast([*]u8, std.heap.c_allocator.alloc(u8, accum_width) catch unreachable);
var b1 = output orelse unreachable;
var b2 = alloc;
@memcpy(b2, accum orelse unreachable, accum_width);
var i: usize = 0;
const size = dict.capacity();
while (i < size) : (i += 1) {
switch (dict.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const key = dict.getKey(i, alignment, key_width, value_width);
const value = dict.getValue(i, alignment, key_width, value_width);
stepper_caller(stepper, key, value, b2, b1);
const temp = b1;
b2 = b1;
b1 = temp;
},
else => {},
}
}
@memcpy(output orelse unreachable, b2, accum_width);
std.heap.c_allocator.free(alloc[0..accum_width]);
const data_bytes = dict.capacity() * slotSize(key_width, value_width);
decref(std.heap.c_allocator, alignment, dict.dict_bytes, data_bytes);
}

View File

@ -0,0 +1,559 @@
const std = @import("std");
const utils = @import("utils.zig");
const RocResult = utils.RocResult;
const mem = std.mem;
const Allocator = mem.Allocator;
const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool;
const Opaque = ?[*]u8;
const Inc = fn (?[*]u8) callconv(.C) void;
const Dec = fn (?[*]u8) callconv(.C) void;
pub const RocList = extern struct {
bytes: ?[*]u8,
length: usize,
pub fn len(self: RocList) usize {
return self.length;
}
pub fn isEmpty(self: RocList) bool {
return self.len() == 0;
}
pub fn empty() RocList {
return RocList{ .bytes = null, .length = 0 };
}
pub fn isUnique(self: RocList) bool {
// the empty list is unique (in the sense that copying it will not leak memory)
if (self.isEmpty()) {
return true;
}
// otherwise, check if the refcount is one
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, self.bytes));
return (ptr - 1)[0] == utils.REFCOUNT_ONE;
}
pub fn allocate(
allocator: *Allocator,
alignment: usize,
length: usize,
element_size: usize,
) RocList {
const data_bytes = length * element_size;
return RocList{
.bytes = utils.allocateWithRefcount(allocator, alignment, data_bytes),
.length = length,
};
}
pub fn makeUnique(self: RocList, allocator: *Allocator, alignment: usize, element_width: usize) RocList {
if (self.isEmpty()) {
return self;
}
if (self.isUnique()) {
return self;
}
// unfortunately, we have to clone
var new_list = RocList.allocate(allocator, alignment, self.length, element_width);
var old_bytes: [*]u8 = @ptrCast([*]u8, self.bytes);
var new_bytes: [*]u8 = @ptrCast([*]u8, new_list.bytes);
const number_of_bytes = self.len() * element_width;
@memcpy(new_bytes, old_bytes, number_of_bytes);
// NOTE we fuse an increment of all keys/values with a decrement of the input dict
const data_bytes = self.len() * element_width;
utils.decref(allocator, alignment, self.bytes, data_bytes);
return new_list;
}
pub fn reallocate(
self: RocList,
allocator: *Allocator,
alignment: usize,
new_length: usize,
element_width: usize,
) RocList {
const old_length = self.length;
const delta_length = new_length - old_length;
const data_bytes = new_length * element_width;
const first_slot = utils.allocateWithRefcount(allocator, alignment, data_bytes);
// transfer the memory
if (self.bytes) |source_ptr| {
const dest_ptr = first_slot;
@memcpy(dest_ptr, source_ptr, old_length * element_width);
@memset(dest_ptr + old_length * element_width, 0, delta_length * element_width);
}
// NOTE the newly added elements are left uninitialized
const result = RocList{
.bytes = first_slot,
.length = new_length,
};
// NOTE we fuse an increment of all keys/values with a decrement of the input dict
utils.decref(allocator, alignment, self.bytes, old_length * element_width);
return result;
}
};
const Caller1 = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller2 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller3 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
pub fn listMap(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, old_element_width: usize, new_element_width: usize) callconv(.C) RocList {
if (list.bytes) |source_ptr| {
const size = list.len();
var i: usize = 0;
const output = RocList.allocate(std.heap.c_allocator, alignment, size, new_element_width);
const target_ptr = output.bytes orelse unreachable;
while (i < size) : (i += 1) {
caller(transform, source_ptr + (i * old_element_width), target_ptr + (i * new_element_width));
}
utils.decref(std.heap.c_allocator, alignment, list.bytes, size * old_element_width);
return output;
} else {
return RocList.empty();
}
}
pub fn listMapWithIndex(list: RocList, transform: Opaque, caller: Caller2, alignment: usize, old_element_width: usize, new_element_width: usize) callconv(.C) RocList {
if (list.bytes) |source_ptr| {
const size = list.len();
var i: usize = 0;
const output = RocList.allocate(std.heap.c_allocator, alignment, size, new_element_width);
const target_ptr = output.bytes orelse unreachable;
while (i < size) : (i += 1) {
caller(transform, @ptrCast(?[*]u8, &i), source_ptr + (i * old_element_width), target_ptr + (i * new_element_width));
}
utils.decref(std.heap.c_allocator, alignment, list.bytes, size * old_element_width);
return output;
} else {
return RocList.empty();
}
}
pub fn listMap2(list1: RocList, list2: RocList, transform: Opaque, caller: Caller2, alignment: usize, a_width: usize, b_width: usize, c_width: usize, dec_a: Dec, dec_b: Dec) callconv(.C) RocList {
const output_length = std.math.min(list1.len(), list2.len());
if (list1.bytes) |source_a| {
if (list2.bytes) |source_b| {
const output = RocList.allocate(std.heap.c_allocator, alignment, output_length, c_width);
const target_ptr = output.bytes orelse unreachable;
var i: usize = 0;
while (i < output_length) : (i += 1) {
const element_a = source_a + i * a_width;
const element_b = source_b + i * b_width;
const target = target_ptr + i * c_width;
caller(transform, element_a, element_b, target);
}
// if the lists don't have equal length, we must consume the remaining elements
// In this case we consume by (recursively) decrementing the elements
if (list1.len() > output_length) {
while (i < list1.len()) : (i += 1) {
const element_a = source_a + i * a_width;
dec_a(element_a);
}
} else if (list2.len() > output_length) {
while (i < list2.len()) : (i += 1) {
const element_b = source_b + i * b_width;
dec_b(element_b);
}
}
utils.decref(std.heap.c_allocator, alignment, list1.bytes, list1.len() * a_width);
utils.decref(std.heap.c_allocator, alignment, list2.bytes, list2.len() * b_width);
return output;
} else {
// consume list1 elements (we know there is at least one because the list1.bytes pointer is non-null
var i: usize = 0;
while (i < list1.len()) : (i += 1) {
const element_a = source_a + i * a_width;
dec_a(element_a);
}
utils.decref(std.heap.c_allocator, alignment, list1.bytes, list1.len() * a_width);
return RocList.empty();
}
} else {
// consume list2 elements (if any)
if (list2.bytes) |source_b| {
var i: usize = 0;
while (i < list2.len()) : (i += 1) {
const element_b = source_b + i * b_width;
dec_b(element_b);
}
utils.decref(std.heap.c_allocator, alignment, list2.bytes, list2.len() * b_width);
}
return RocList.empty();
}
}
pub fn listMap3(list1: RocList, list2: RocList, list3: RocList, transform: Opaque, caller: Caller3, alignment: usize, a_width: usize, b_width: usize, c_width: usize, d_width: usize, dec_a: Dec, dec_b: Dec, dec_c: Dec) callconv(.C) RocList {
const smaller_length = std.math.min(list1.len(), list2.len());
const output_length = std.math.min(smaller_length, list3.len());
if (list1.bytes) |source_a| {
if (list2.bytes) |source_b| {
if (list3.bytes) |source_c| {
const output = RocList.allocate(std.heap.c_allocator, alignment, output_length, d_width);
const target_ptr = output.bytes orelse unreachable;
var i: usize = 0;
while (i < output_length) : (i += 1) {
const element_a = source_a + i * a_width;
const element_b = source_b + i * b_width;
const element_c = source_c + i * c_width;
const target = target_ptr + i * d_width;
caller(transform, element_a, element_b, element_c, target);
}
// if the lists don't have equal length, we must consume the remaining elements
// In this case we consume by (recursively) decrementing the elements
if (list1.len() > output_length) {
i = output_length;
while (i < list1.len()) : (i += 1) {
const element_a = source_a + i * a_width;
dec_a(element_a);
}
}
if (list2.len() > output_length) {
i = output_length;
while (i < list2.len()) : (i += 1) {
const element_b = source_b + i * b_width;
dec_b(element_b);
}
}
if (list3.len() > output_length) {
i = output_length;
while (i < list3.len()) : (i += 1) {
const element_c = source_c + i * c_width;
dec_c(element_c);
}
}
utils.decref(std.heap.c_allocator, alignment, list1.bytes, list1.len() * a_width);
utils.decref(std.heap.c_allocator, alignment, list2.bytes, list2.len() * b_width);
utils.decref(std.heap.c_allocator, alignment, list3.bytes, list3.len() * c_width);
return output;
} else {
// consume list1 elements (we know there is at least one because the list1.bytes pointer is non-null
var i: usize = 0;
while (i < list1.len()) : (i += 1) {
const element_a = source_a + i * a_width;
dec_a(element_a);
}
utils.decref(std.heap.c_allocator, alignment, list1.bytes, list1.len() * a_width);
// consume list2 elements (we know there is at least one because the list1.bytes pointer is non-null
i = 0;
while (i < list2.len()) : (i += 1) {
const element_b = source_b + i * b_width;
dec_b(element_b);
}
utils.decref(std.heap.c_allocator, alignment, list2.bytes, list2.len() * b_width);
return RocList.empty();
}
} else {
// consume list1 elements (we know there is at least one because the list1.bytes pointer is non-null
var i: usize = 0;
while (i < list1.len()) : (i += 1) {
const element_a = source_a + i * a_width;
dec_a(element_a);
}
utils.decref(std.heap.c_allocator, alignment, list1.bytes, list1.len() * a_width);
// consume list3 elements (if any)
if (list3.bytes) |source_c| {
i = 0;
while (i < list2.len()) : (i += 1) {
const element_c = source_c + i * c_width;
dec_c(element_c);
}
utils.decref(std.heap.c_allocator, alignment, list3.bytes, list3.len() * c_width);
}
return RocList.empty();
}
} else {
// consume list2 elements (if any)
if (list2.bytes) |source_b| {
var i: usize = 0;
while (i < list2.len()) : (i += 1) {
const element_b = source_b + i * b_width;
dec_b(element_b);
}
utils.decref(std.heap.c_allocator, alignment, list2.bytes, list2.len() * b_width);
}
// consume list3 elements (if any)
if (list3.bytes) |source_c| {
var i: usize = 0;
while (i < list2.len()) : (i += 1) {
const element_c = source_c + i * c_width;
dec_c(element_c);
}
utils.decref(std.heap.c_allocator, alignment, list3.bytes, list3.len() * c_width);
}
return RocList.empty();
}
}
pub fn listKeepIf(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, element_width: usize, inc: Inc, dec: Dec) callconv(.C) RocList {
if (list.bytes) |source_ptr| {
const size = list.len();
var i: usize = 0;
var output = RocList.allocate(std.heap.c_allocator, alignment, list.len(), list.len() * element_width);
const target_ptr = output.bytes orelse unreachable;
var kept: usize = 0;
while (i < size) : (i += 1) {
var keep = false;
const element = source_ptr + (i * element_width);
inc(element);
caller(transform, element, @ptrCast(?[*]u8, &keep));
if (keep) {
@memcpy(target_ptr + (kept * element_width), element, element_width);
kept += 1;
} else {
dec(element);
}
}
// consume the input list
utils.decref(std.heap.c_allocator, alignment, list.bytes, size * element_width);
if (kept == 0) {
// if the output is empty, deallocate the space we made for the result
utils.decref(std.heap.c_allocator, alignment, output.bytes, size * element_width);
return RocList.empty();
} else {
output.length = kept;
return output;
}
} else {
return RocList.empty();
}
}
pub fn listKeepOks(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, before_width: usize, result_width: usize, after_width: usize, inc_closure: Inc, dec_result: Dec) callconv(.C) RocList {
return listKeepResult(list, RocResult.isOk, transform, caller, alignment, before_width, result_width, after_width, inc_closure, dec_result);
}
pub fn listKeepErrs(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, before_width: usize, result_width: usize, after_width: usize, inc_closure: Inc, dec_result: Dec) callconv(.C) RocList {
return listKeepResult(list, RocResult.isErr, transform, caller, alignment, before_width, result_width, after_width, inc_closure, dec_result);
}
pub fn listKeepResult(list: RocList, is_good_constructor: fn (RocResult) bool, transform: Opaque, caller: Caller1, alignment: usize, before_width: usize, result_width: usize, after_width: usize, inc_closure: Inc, dec_result: Dec) RocList {
if (list.bytes) |source_ptr| {
const size = list.len();
var i: usize = 0;
var output = RocList.allocate(std.heap.c_allocator, alignment, list.len(), list.len() * after_width);
const target_ptr = output.bytes orelse unreachable;
var temporary = @ptrCast([*]u8, std.heap.c_allocator.alloc(u8, result_width) catch unreachable);
var kept: usize = 0;
while (i < size) : (i += 1) {
const before_element = source_ptr + (i * before_width);
inc_closure(transform);
caller(transform, before_element, temporary);
const result = utils.RocResult{ .bytes = temporary };
const after_element = temporary + @sizeOf(i64);
if (is_good_constructor(result)) {
@memcpy(target_ptr + (kept * after_width), after_element, after_width);
kept += 1;
} else {
dec_result(temporary);
}
}
utils.decref(std.heap.c_allocator, alignment, list.bytes, size * before_width);
std.heap.c_allocator.free(temporary[0..result_width]);
if (kept == 0) {
utils.decref(std.heap.c_allocator, alignment, output.bytes, size * after_width);
return RocList.empty();
} else {
output.length = kept;
return output;
}
} else {
return RocList.empty();
}
}
pub fn listWalk(list: RocList, stepper: Opaque, stepper_caller: Caller2, accum: Opaque, alignment: usize, element_width: usize, accum_width: usize, output: Opaque) callconv(.C) void {
if (accum_width == 0) {
return;
}
if (list.isEmpty()) {
@memcpy(output orelse unreachable, accum orelse unreachable, accum_width);
return;
}
const alloc: [*]u8 = @ptrCast([*]u8, std.heap.c_allocator.alloc(u8, accum_width) catch unreachable);
var b1 = output orelse unreachable;
var b2 = alloc;
@memcpy(b2, accum orelse unreachable, accum_width);
if (list.bytes) |source_ptr| {
var i: usize = 0;
const size = list.len();
while (i < size) : (i += 1) {
const element = source_ptr + i * element_width;
stepper_caller(stepper, element, b2, b1);
const temp = b1;
b2 = b1;
b1 = temp;
}
}
@memcpy(output orelse unreachable, b2, accum_width);
std.heap.c_allocator.free(alloc[0..accum_width]);
const data_bytes = list.len() * element_width;
utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes);
}
pub fn listWalkBackwards(list: RocList, stepper: Opaque, stepper_caller: Caller2, accum: Opaque, alignment: usize, element_width: usize, accum_width: usize, output: Opaque) callconv(.C) void {
if (accum_width == 0) {
return;
}
if (list.isEmpty()) {
@memcpy(output orelse unreachable, accum orelse unreachable, accum_width);
return;
}
const alloc: [*]u8 = @ptrCast([*]u8, std.heap.c_allocator.alloc(u8, accum_width) catch unreachable);
var b1 = output orelse unreachable;
var b2 = alloc;
@memcpy(b2, accum orelse unreachable, accum_width);
if (list.bytes) |source_ptr| {
const size = list.len();
var i: usize = size;
while (i > 0) {
i -= 1;
const element = source_ptr + i * element_width;
stepper_caller(stepper, element, b2, b1);
const temp = b1;
b2 = b1;
b1 = temp;
}
const data_bytes = list.len() * element_width;
utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes);
}
@memcpy(output orelse unreachable, b2, accum_width);
std.heap.c_allocator.free(alloc[0..accum_width]);
const data_bytes = list.len() * element_width;
utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes);
}
// List.contains : List k, k -> Bool
pub fn listContains(list: RocList, key: Opaque, key_width: usize, is_eq: EqFn) callconv(.C) bool {
if (list.bytes) |source_ptr| {
const size = list.len();
var i: usize = 0;
while (i < size) : (i += 1) {
const element = source_ptr + i * key_width;
if (is_eq(element, key)) {
return true;
}
}
}
return false;
}
pub fn listRepeat(count: usize, alignment: usize, element: Opaque, element_width: usize, inc_n_element: Inc) callconv(.C) RocList {
if (count == 0) {
return RocList.empty();
}
const allocator = std.heap.c_allocator;
var output = RocList.allocate(allocator, alignment, count, element_width);
if (output.bytes) |target_ptr| {
var i: usize = 0;
const source = element orelse unreachable;
while (i < count) : (i += 1) {
@memcpy(target_ptr + i * element_width, source, element_width);
}
// TODO do all increments at once!
i = 0;
while (i < count) : (i += 1) {
inc_n_element(element);
}
return output;
} else {
unreachable;
}
}
pub fn listAppend(list: RocList, alignment: usize, element: Opaque, element_width: usize) callconv(.C) RocList {
const old_length = list.len();
var output = list.reallocate(std.heap.c_allocator, alignment, old_length + 1, element_width);
if (output.bytes) |target| {
if (element) |source| {
@memcpy(target + old_length * element_width, source, element_width);
}
}
return output;
}

View File

@ -2,6 +2,24 @@ const builtin = @import("builtin");
const std = @import("std");
const testing = std.testing;
// List Module
const list = @import("list.zig");
comptime {
exportListFn(list.listMap, "map");
exportListFn(list.listMap2, "map2");
exportListFn(list.listMap3, "map3");
exportListFn(list.listMapWithIndex, "map_with_index");
exportListFn(list.listKeepIf, "keep_if");
exportListFn(list.listWalk, "walk");
exportListFn(list.listWalkBackwards, "walk_backwards");
exportListFn(list.listKeepOks, "keep_oks");
exportListFn(list.listKeepErrs, "keep_errs");
exportListFn(list.listContains, "contains");
exportListFn(list.listRepeat, "repeat");
exportListFn(list.listAppend, "append");
}
// Dict Module
const dict = @import("dict.zig");
const hash = @import("hash.zig");
@ -16,6 +34,12 @@ comptime {
exportDictFn(dict.elementsRc, "elementsRc");
exportDictFn(dict.dictKeys, "keys");
exportDictFn(dict.dictValues, "values");
exportDictFn(dict.dictUnion, "union");
exportDictFn(dict.dictIntersection, "intersection");
exportDictFn(dict.dictDifference, "difference");
exportDictFn(dict.dictWalk, "walk");
exportDictFn(dict.setFromList, "set_from_list");
exportDictFn(hash.wyhash, "hash");
exportDictFn(hash.wyhash_rocstr, "hash_str");
@ -34,6 +58,7 @@ comptime {
// Str Module
const str = @import("str.zig");
comptime {
exportStrFn(str.init, "init");
exportStrFn(str.strSplitInPlaceC, "str_split_in_place");
exportStrFn(str.countSegments, "count_segments");
exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters");
@ -43,7 +68,10 @@ comptime {
exportStrFn(str.strJoinWithC, "joinWith");
exportStrFn(str.strNumberOfBytes, "number_of_bytes");
exportStrFn(str.strFromIntC, "from_int");
exportStrFn(str.strFromFloatC, "from_float");
exportStrFn(str.strEqual, "equal");
exportStrFn(str.strToBytesC, "to_bytes");
exportStrFn(str.fromUtf8C, "from_utf8");
}
// Export helpers - Must be run inside a comptime
@ -60,6 +88,10 @@ fn exportDictFn(comptime func: anytype, comptime func_name: []const u8) void {
exportBuiltinFn(func, "dict." ++ func_name);
}
fn exportListFn(comptime func: anytype, comptime func_name: []const u8) void {
exportBuiltinFn(func, "list." ++ func_name);
}
// Run all tests in imported modules
// https://github.com/ziglang/zig/blob/master/lib/std/std.zig#L94
test "" {

View File

@ -1,22 +1,23 @@
const std = @import("std");
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
const math = std.math;
pub fn atan(num: f64) callconv(.C) f64 {
return math.atan(num);
return @call(.{ .modifier = always_inline }, math.atan, .{num});
}
pub fn isFinite(num: f64) callconv(.C) bool {
return math.isFinite(num);
return @call(.{ .modifier = always_inline }, math.isFinite, .{num});
}
pub fn powInt(base: i64, exp: i64) callconv(.C) i64 {
return math.pow(i64, base, exp);
return @call(.{ .modifier = always_inline }, math.pow, .{ i64, base, exp });
}
pub fn acos(num: f64) callconv(.C) f64 {
return math.acos(num);
return @call(.{ .modifier = always_inline }, math.acos, .{num});
}
pub fn asin(num: f64) callconv(.C) f64 {
return math.asin(num);
return @call(.{ .modifier = always_inline }, math.asin, .{num});
}

View File

@ -1,3 +1,5 @@
const utils = @import("utils.zig");
const RocList = @import("list.zig").RocList;
const std = @import("std");
const mem = std.mem;
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
@ -5,6 +7,7 @@ const Allocator = mem.Allocator;
const unicode = std.unicode;
const testing = std.testing;
const expectEqual = testing.expectEqual;
const expectError = testing.expectError;
const expect = testing.expect;
const InPlace = packed enum(u8) {
@ -12,6 +15,7 @@ const InPlace = packed enum(u8) {
Clone,
};
const SMALL_STR_MAX_LENGTH = small_string_size - 1;
const small_string_size = 2 * @sizeOf(usize);
const blank_small_string: [16]u8 = init_blank_small_string(small_string_size);
@ -47,18 +51,7 @@ pub const RocStr = extern struct {
}
pub fn initBig(allocator: *Allocator, in_place: InPlace, number_of_chars: u64) RocStr {
const length = @sizeOf(usize) + number_of_chars;
var new_bytes: []usize = allocator.alloc(usize, length) catch unreachable;
if (in_place == InPlace.InPlace) {
new_bytes[0] = @intCast(usize, number_of_chars);
} else {
const v: isize = std.math.minInt(isize);
new_bytes[0] = @bitCast(usize, v);
}
var first_element = @ptrCast([*]align(@alignOf(usize)) u8, new_bytes);
first_element += @sizeOf(usize);
const first_element = utils.allocateWithRefcount(allocator, @sizeOf(usize), number_of_chars);
return RocStr{
.str_bytes = first_element,
@ -95,6 +88,12 @@ pub const RocStr = extern struct {
}
}
pub fn toSlice(self: RocStr) []u8 {
const str_bytes_ptr: [*]u8 = self.str_bytes orelse unreachable;
const str_bytes: []u8 = str_bytes_ptr[0..self.str_len];
return str_bytes;
}
// This takes ownership of the pointed-to bytes if they won't fit in a
// small string, and returns a (pointer, len) tuple which points to them.
pub fn withCapacity(length: usize) RocStr {
@ -266,6 +265,10 @@ pub const RocStr = extern struct {
}
};
pub fn init(bytes_ptr: [*]const u8, length: usize) callconv(.C) RocStr {
return @call(.{ .modifier = always_inline }, RocStr.init, .{ std.heap.c_allocator, bytes_ptr, length });
}
// Str.equal
pub fn strEqual(self: RocStr, other: RocStr) callconv(.C) bool {
return self.eq(other);
@ -302,6 +305,23 @@ fn strFromIntHelp(allocator: *Allocator, comptime T: type, int: T) RocStr {
return RocStr.init(allocator, &buf, result.len);
}
// Str.fromFloat
// When we actually use this in Roc, libc will be linked so we have access to std.heap.c_allocator
pub fn strFromFloatC(float: f64) callconv(.C) RocStr {
// NOTE the compiled zig for float formatting seems to use LLVM11-specific features
// hopefully we can use zig instead of snprintf in the future when we upgrade
const c = @cImport({
// See https://github.com/ziglang/zig/issues/515
@cDefine("_NO_CRT_STDIO_INLINE", "1");
@cInclude("stdio.h");
});
var buf: [100]u8 = undefined;
const result = c.snprintf(&buf, 100, "%f", float);
return RocStr.init(std.heap.c_allocator, &buf, @intCast(usize, result));
}
// Str.split
// When we actually use this in Roc, libc will be linked so we have access to std.heap.c_allocator
pub fn strSplitInPlaceC(array: [*]RocStr, string: RocStr, delimiter: RocStr) callconv(.C) void {
@ -816,8 +836,10 @@ pub fn strConcatC(result_in_place: InPlace, arg1: RocStr, arg2: RocStr) callconv
fn strConcat(allocator: *Allocator, result_in_place: InPlace, arg1: RocStr, arg2: RocStr) RocStr {
if (arg1.isEmpty()) {
// the second argument is borrowed, so we must increment its refcount before returning
return RocStr.clone(allocator, result_in_place, arg2);
} else if (arg2.isEmpty()) {
// the first argument is owned, so we can return it without cloning
return RocStr.clone(allocator, result_in_place, arg1);
} else {
const combined_length = arg1.len() + arg2.len();
@ -940,3 +962,293 @@ test "RocStr.joinWith: result is big" {
expect(roc_result.eq(result));
}
// Str.toBytes
pub fn strToBytesC(arg: RocStr) callconv(.C) RocList {
return @call(.{ .modifier = always_inline }, strToBytes, .{ std.heap.c_allocator, arg });
}
fn strToBytes(allocator: *Allocator, arg: RocStr) RocList {
if (arg.isEmpty()) {
return RocList.empty();
} else if (arg.isSmallStr()) {
const length = arg.len();
const ptr = utils.allocateWithRefcount(allocator, @alignOf(usize), length);
@memcpy(ptr, arg.asU8ptr(), length);
return RocList{ .length = length, .bytes = ptr };
} else {
return RocList{ .length = arg.len(), .bytes = arg.str_bytes };
}
}
const FromUtf8Result = extern struct {
byte_index: usize,
string: RocStr,
is_ok: bool,
problem_code: Utf8ByteProblem,
};
pub fn fromUtf8C(arg: RocList, output: *FromUtf8Result) callconv(.C) void {
output.* = @call(.{ .modifier = always_inline }, fromUtf8, .{ std.heap.c_allocator, arg });
}
fn fromUtf8(allocator: *Allocator, arg: RocList) FromUtf8Result {
const bytes = @ptrCast([*]const u8, arg.bytes)[0..arg.length];
if (unicode.utf8ValidateSlice(bytes)) {
// the output will be correct. Now we need to take ownership of the input
if (arg.len() <= SMALL_STR_MAX_LENGTH) {
// turn the bytes into a small string
const string = RocStr.init(allocator, @ptrCast([*]u8, arg.bytes), arg.len());
// then decrement the input list
const data_bytes = arg.len();
utils.decref(allocator, @alignOf(usize), arg.bytes, data_bytes);
return FromUtf8Result{ .is_ok = true, .string = string, .byte_index = 0, .problem_code = Utf8ByteProblem.InvalidStartByte };
} else {
const byte_list = arg.makeUnique(allocator, @alignOf(usize), @sizeOf(u8));
const string = RocStr{ .str_bytes = byte_list.bytes, .str_len = byte_list.length };
return FromUtf8Result{ .is_ok = true, .string = string, .byte_index = 0, .problem_code = Utf8ByteProblem.InvalidStartByte };
}
} else {
const temp = errorToProblem(@ptrCast([*]u8, arg.bytes), arg.length);
// consume the input list
const data_bytes = arg.len();
utils.decref(allocator, @alignOf(usize), arg.bytes, data_bytes);
return FromUtf8Result{ .is_ok = false, .string = RocStr.empty(), .byte_index = temp.index, .problem_code = temp.problem };
}
}
fn errorToProblem(bytes: [*]u8, length: usize) struct { index: usize, problem: Utf8ByteProblem } {
var index: usize = 0;
while (index < length) {
const nextNumBytes = numberOfNextCodepointBytes(bytes, length, index) catch |err| {
switch (err) {
error.UnexpectedEof => {
return .{ .index = index, .problem = Utf8ByteProblem.UnexpectedEndOfSequence };
},
error.Utf8InvalidStartByte => return .{ .index = index, .problem = Utf8ByteProblem.InvalidStartByte },
error.Utf8ExpectedContinuation => return .{ .index = index, .problem = Utf8ByteProblem.ExpectedContinuation },
error.Utf8OverlongEncoding => return .{ .index = index, .problem = Utf8ByteProblem.OverlongEncoding },
error.Utf8EncodesSurrogateHalf => return .{ .index = index, .problem = Utf8ByteProblem.EncodesSurrogateHalf },
error.Utf8CodepointTooLarge => return .{ .index = index, .problem = Utf8ByteProblem.CodepointTooLarge },
}
};
index += nextNumBytes;
}
unreachable;
}
pub fn isValidUnicode(ptr: [*]u8, len: usize) callconv(.C) bool {
const bytes: []u8 = ptr[0..len];
return @call(.{ .modifier = always_inline }, unicode.utf8ValidateSlice, .{bytes});
}
const Utf8DecodeError = error{
UnexpectedEof,
Utf8InvalidStartByte,
Utf8ExpectedContinuation,
Utf8OverlongEncoding,
Utf8EncodesSurrogateHalf,
Utf8CodepointTooLarge,
};
// Essentially unicode.utf8ValidateSlice -> https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L156
// but only for the next codepoint from the index. Then we return the number of bytes of that codepoint.
// TODO: we only ever use the values 0-4, so can we use smaller int than `usize`?
pub fn numberOfNextCodepointBytes(ptr: [*]u8, len: usize, index: usize) Utf8DecodeError!usize {
const codepoint_len = try unicode.utf8ByteSequenceLength(ptr[index]);
const codepoint_end_index = index + codepoint_len;
if (codepoint_end_index > len) {
return error.UnexpectedEof;
}
_ = try unicode.utf8Decode(ptr[index..codepoint_end_index]);
return codepoint_end_index - index;
}
// Return types for validateUtf8Bytes
// Values must be in alphabetical order. That is, lowest values are the first alphabetically.
pub const Utf8ByteProblem = packed enum(u8) {
CodepointTooLarge = 0,
EncodesSurrogateHalf = 1,
ExpectedContinuation = 2,
InvalidStartByte = 3,
OverlongEncoding = 4,
UnexpectedEndOfSequence = 5,
};
fn validateUtf8Bytes(bytes: [*]u8, length: usize) FromUtf8Result {
return fromUtf8(std.testing.allocator, RocList{ .bytes = bytes, .length = length });
}
fn validateUtf8BytesX(str: RocList) FromUtf8Result {
return fromUtf8(std.testing.allocator, str);
}
fn expectOk(result: FromUtf8Result) void {
expectEqual(result.is_ok, true);
}
fn sliceHelp(bytes: [*]const u8, length: usize) RocList {
var list = RocList.allocate(testing.allocator, @alignOf(usize), length, @sizeOf(u8));
@memcpy(list.bytes orelse unreachable, bytes, length);
list.length = length;
return list;
}
fn toErrUtf8ByteResponse(index: usize, problem: Utf8ByteProblem) FromUtf8Result {
return FromUtf8Result{ .is_ok = false, .string = RocStr.empty(), .byte_index = index, .problem_code = problem };
}
// NOTE on memory: the validate function consumes a RC token of the input. Since
// we freshly created it (in `sliceHelp`), it has only one RC token, and input list will be deallocated.
//
// If we tested with big strings, we'd have to deallocate the output string, but never the input list
test "validateUtf8Bytes: ascii" {
const raw = "abc";
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectOk(validateUtf8BytesX(list));
}
test "validateUtf8Bytes: unicode œ" {
const raw = "œ";
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectOk(validateUtf8BytesX(list));
}
test "validateUtf8Bytes: unicode ∆" {
const raw = "";
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectOk(validateUtf8BytesX(list));
}
test "validateUtf8Bytes: emoji" {
const raw = "💖";
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectOk(validateUtf8BytesX(list));
}
test "validateUtf8Bytes: unicode ∆ in middle of array" {
const raw = "œb∆c¬";
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectOk(validateUtf8BytesX(list));
}
fn expectErr(list: RocList, index: usize, err: Utf8DecodeError, problem: Utf8ByteProblem) void {
const str_ptr = @ptrCast([*]u8, list.bytes);
const str_len = list.length;
expectError(err, numberOfNextCodepointBytes(str_ptr, str_len, index));
expectEqual(toErrUtf8ByteResponse(index, problem), validateUtf8Bytes(str_ptr, str_len));
}
test "validateUtf8Bytes: invalid start byte" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L426
const raw = "ab\x80c";
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 2, error.Utf8InvalidStartByte, Utf8ByteProblem.InvalidStartByte);
}
test "validateUtf8Bytes: unexpected eof for 2 byte sequence" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L426
const raw = "abc\xc2";
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 3, error.UnexpectedEof, Utf8ByteProblem.UnexpectedEndOfSequence);
}
test "validateUtf8Bytes: expected continuation for 2 byte sequence" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L426
const raw = "abc\xc2\x00";
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 3, error.Utf8ExpectedContinuation, Utf8ByteProblem.ExpectedContinuation);
}
test "validateUtf8Bytes: unexpected eof for 3 byte sequence" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L430
const raw = "abc\xe0\x00";
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 3, error.UnexpectedEof, Utf8ByteProblem.UnexpectedEndOfSequence);
}
test "validateUtf8Bytes: expected continuation for 3 byte sequence" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L430
const raw = "abc\xe0\xa0\xc0";
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 3, error.Utf8ExpectedContinuation, Utf8ByteProblem.ExpectedContinuation);
}
test "validateUtf8Bytes: unexpected eof for 4 byte sequence" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L437
const raw = "abc\xf0\x90\x00";
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 3, error.UnexpectedEof, Utf8ByteProblem.UnexpectedEndOfSequence);
}
test "validateUtf8Bytes: expected continuation for 4 byte sequence" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L437
const raw = "abc\xf0\x90\x80\x00";
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 3, error.Utf8ExpectedContinuation, Utf8ByteProblem.ExpectedContinuation);
}
test "validateUtf8Bytes: overlong" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L451
const raw = "abc\xf0\x80\x80\x80";
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 3, error.Utf8OverlongEncoding, Utf8ByteProblem.OverlongEncoding);
}
test "validateUtf8Bytes: codepoint out too large" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L465
const raw = "abc\xf4\x90\x80\x80";
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 3, error.Utf8CodepointTooLarge, Utf8ByteProblem.CodepointTooLarge);
}
test "validateUtf8Bytes: surrogate halves" {
// https://github.com/ziglang/zig/blob/0.7.x/lib/std/unicode.zig#L468
const raw = "abc\xed\xa0\x80";
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 3, error.Utf8EncodesSurrogateHalf, Utf8ByteProblem.EncodesSurrogateHalf);
}

View File

@ -0,0 +1,107 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const REFCOUNT_ONE_ISIZE: comptime isize = std.math.minInt(isize);
pub const REFCOUNT_ONE: usize = @bitCast(usize, REFCOUNT_ONE_ISIZE);
pub fn decref(
allocator: *Allocator,
alignment: usize,
bytes_or_null: ?[*]u8,
data_bytes: usize,
) void {
if (data_bytes == 0) {
return;
}
var bytes = bytes_or_null orelse return;
const isizes: [*]isize = @ptrCast([*]isize, @alignCast(8, bytes));
const refcount = (isizes - 1)[0];
const refcount_isize = @bitCast(isize, refcount);
switch (alignment) {
16 => {
if (refcount == REFCOUNT_ONE_ISIZE) {
allocator.free((bytes - 16)[0 .. 16 + data_bytes]);
} else if (refcount_isize < 0) {
(isizes - 1)[0] = refcount - 1;
}
},
else => {
// NOTE enums can currently have an alignment of < 8
if (refcount == REFCOUNT_ONE_ISIZE) {
allocator.free((bytes - 8)[0 .. 8 + data_bytes]);
} else if (refcount_isize < 0) {
(isizes - 1)[0] = refcount - 1;
}
},
}
}
pub fn allocateWithRefcount(
allocator: *Allocator,
alignment: usize,
data_bytes: usize,
) [*]u8 {
comptime const result_in_place = false;
switch (alignment) {
16 => {
const length = 2 * @sizeOf(usize) + data_bytes;
var new_bytes: []align(16) u8 = allocator.alignedAlloc(u8, 16, length) catch unreachable;
var as_usize_array = @ptrCast([*]usize, new_bytes);
if (result_in_place) {
as_usize_array[0] = 0;
as_usize_array[1] = @intCast(usize, number_of_slots);
} else {
as_usize_array[0] = 0;
as_usize_array[1] = REFCOUNT_ONE;
}
var as_u8_array = @ptrCast([*]u8, new_bytes);
const first_slot = as_u8_array + 2 * @sizeOf(usize);
return first_slot;
},
else => {
const length = @sizeOf(usize) + data_bytes;
var new_bytes: []align(8) u8 = allocator.alignedAlloc(u8, 8, length) catch unreachable;
var as_usize_array = @ptrCast([*]isize, new_bytes);
if (result_in_place) {
as_usize_array[0] = @intCast(isize, number_of_slots);
} else {
as_usize_array[0] = REFCOUNT_ONE_ISIZE;
}
var as_u8_array = @ptrCast([*]u8, new_bytes);
const first_slot = as_u8_array + @sizeOf(usize);
return first_slot;
},
}
}
pub const RocResult = extern struct {
bytes: ?[*]u8,
pub fn isOk(self: RocResult) bool {
// assumptions
//
// - the tag is the first field
// - the tag is usize bytes wide
// - Ok has tag_id 1, because Err < Ok
const usizes: [*]usize = @ptrCast([*]usize, @alignCast(8, self.bytes));
return usizes[0] == 1;
}
pub fn isErr(self: RocResult) bool {
return !self.isOk();
}
};

View File

@ -14,13 +14,23 @@ fn main() {
let build_script_dir_path = fs::canonicalize(Path::new(".")).unwrap();
let bitcode_path = build_script_dir_path.join("bitcode");
let src_path = bitcode_path.join("src");
let src_obj_path = bitcode_path.join("builtins.o");
let src_obj = src_obj_path.to_str().expect("Invalid src object path");
println!("Compiling zig object to: {}", src_obj);
run_command(&bitcode_path, "zig", &["build", "object", "-Drelease=true"]);
let dest_obj_path = Path::new(&out_dir).join("builtins.o");
let dest_obj = dest_obj_path.to_str().expect("Invalid dest object path");
println!("Moving zig object to: {}", dest_obj);
run_command(&bitcode_path, "mv", &[src_obj, dest_obj]);
let dest_ir_path = bitcode_path.join("builtins.ll");
let dest_ir = dest_ir_path.to_str().expect("Invalid dest ir path");
println!("Compiling ir to: {}", dest_ir);
run_command(bitcode_path, "zig", &["build", "ir", "-Drelease=true"]);
run_command(&bitcode_path, "zig", &["build", "ir", "-Drelease=true"]);
let dest_bc_path = Path::new(&out_dir).join("builtins.bc");
let dest_bc = dest_bc_path.to_str().expect("Invalid dest bc path");
@ -34,7 +44,8 @@ fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rustc-env=BUILTINS_BC={}", dest_bc);
get_zig_files(src_path.as_path(), &|path| {
println!("cargo:rustc-env=BUILTINS_O={}", dest_obj);
get_zig_files(bitcode_path.as_path(), &|path| {
let path: &Path = path;
println!(
"cargo:rerun-if-changed={}",
@ -74,7 +85,9 @@ fn get_zig_files(dir: &Path, cb: &dyn Fn(&Path)) -> io::Result<()> {
let entry = entry?;
let path_buf = entry.path();
if path_buf.is_dir() {
get_zig_files(&path_buf, cb).unwrap();
if !path_buf.ends_with("zig-cache") {
get_zig_files(&path_buf, cb).unwrap();
}
} else {
let path = path_buf.as_path();

View File

@ -2,15 +2,20 @@ use std::fs::File;
use std::io::prelude::Read;
use std::vec::Vec;
const PATH: &str = env!(
const BC_PATH: &str = env!(
"BUILTINS_BC",
"Env var BUILTINS_BC not found. Is there a problem with the build script?"
);
pub const OBJ_PATH: &str = env!(
"BUILTINS_O",
"Env var BUILTINS_O not found. Is there a problem with the build script?"
);
pub fn get_bytes() -> Vec<u8> {
// In the build script for the builtins module, we compile the builtins bitcode and set
// BUILTINS_BC to the path to the compiled output.
let mut builtins_bitcode = File::open(PATH).expect("Unable to find builtins bitcode source");
let mut builtins_bitcode = File::open(BC_PATH).expect("Unable to find builtins bitcode source");
let mut buffer = Vec::new();
builtins_bitcode
.read_to_end(&mut buffer)
@ -24,6 +29,7 @@ pub const NUM_ATAN: &str = "roc_builtins.num.atan";
pub const NUM_IS_FINITE: &str = "roc_builtins.num.is_finite";
pub const NUM_POW_INT: &str = "roc_builtins.num.pow_int";
pub const STR_INIT: &str = "roc_builtins.str.init";
pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments";
pub const STR_CONCAT: &str = "roc_builtins.str.concat";
pub const STR_JOIN_WITH: &str = "roc_builtins.str.joinWith";
@ -33,7 +39,10 @@ pub const STR_STARTS_WITH: &str = "roc_builtins.str.starts_with";
pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with";
pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes";
pub const STR_FROM_INT: &str = "roc_builtins.str.from_int";
pub const STR_FROM_FLOAT: &str = "roc_builtins.str.from_float";
pub const STR_EQUAL: &str = "roc_builtins.str.equal";
pub const STR_TO_BYTES: &str = "roc_builtins.str.to_bytes";
pub const STR_FROM_UTF8: &str = "roc_builtins.str.from_utf8";
pub const DICT_HASH: &str = "roc_builtins.dict.hash";
pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str";
@ -46,3 +55,22 @@ pub const DICT_GET: &str = "roc_builtins.dict.get";
pub const DICT_ELEMENTS_RC: &str = "roc_builtins.dict.elementsRc";
pub const DICT_KEYS: &str = "roc_builtins.dict.keys";
pub const DICT_VALUES: &str = "roc_builtins.dict.values";
pub const DICT_UNION: &str = "roc_builtins.dict.union";
pub const DICT_DIFFERENCE: &str = "roc_builtins.dict.difference";
pub const DICT_INTERSECTION: &str = "roc_builtins.dict.intersection";
pub const DICT_WALK: &str = "roc_builtins.dict.walk";
pub const SET_FROM_LIST: &str = "roc_builtins.dict.set_from_list";
pub const LIST_MAP: &str = "roc_builtins.list.map";
pub const LIST_MAP2: &str = "roc_builtins.list.map2";
pub const LIST_MAP3: &str = "roc_builtins.list.map3";
pub const LIST_MAP_WITH_INDEX: &str = "roc_builtins.list.map_with_index";
pub const LIST_KEEP_IF: &str = "roc_builtins.list.keep_if";
pub const LIST_KEEP_OKS: &str = "roc_builtins.list.keep_oks";
pub const LIST_KEEP_ERRS: &str = "roc_builtins.list.keep_errs";
pub const LIST_WALK: &str = "roc_builtins.list.walk";
pub const LIST_WALK_BACKWARDS: &str = "roc_builtins.list.walk_backwards";
pub const LIST_CONTAINS: &str = "roc_builtins.list.contains";
pub const LIST_REPEAT: &str = "roc_builtins.list.repeat";
pub const LIST_APPEND: &str = "roc_builtins.list.append";

View File

@ -3,4 +3,3 @@
#![allow(clippy::large_enum_variant)]
pub mod bitcode;
pub mod std;
pub mod unique;

View File

@ -3,13 +3,37 @@ use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_region::all::Region;
use roc_types::builtin_aliases::{
bool_type, dict_type, float_type, int_type, list_type, nat_type, num_type, ordering_type,
result_type, set_type, str_type, u64_type,
bool_type, dict_type, float_type, i128_type, int_type, list_type, nat_type, num_type,
ordering_type, result_type, set_type, str_type, str_utf8_byte_problem_type, u64_type, u8_type,
};
use roc_types::solved_types::SolvedType;
use roc_types::subs::VarId;
use std::collections::HashMap;
/// Example:
///
/// let_tvars! { a, b, c }
///
/// This is equivalent to:
///
/// let a = VarId::from_u32(1);
/// let b = VarId::from_u32(2);
/// let c = VarId::from_u32(3);
///
/// The idea is that this is less error-prone than assigning hardcoded IDs by hand.
macro_rules! let_tvars {
($($name:ident,)+) => { let_tvars!($($name),+) };
($($name:ident),*) => {
let mut _current_tvar = 0;
$(
_current_tvar += 1;
let $name = VarId::from_u32(_current_tvar);
)*
};
}
#[derive(Clone, Copy, Debug)]
pub enum Mode {
Standard,
@ -300,6 +324,48 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
),
);
// bitwiseOr : Int a, Int a -> Int a
add_type(
Symbol::NUM_BITWISE_OR,
top_level_function(
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1))),
),
);
// shiftLeftBy : Int a, Int a -> Int a
add_type(
Symbol::NUM_SHIFT_LEFT,
top_level_function(
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1))),
),
);
// shiftRightBy : Int a, Int a -> Int a
add_type(
Symbol::NUM_SHIFT_RIGHT,
top_level_function(
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1))),
),
);
// shiftRightZfBy : Int a, Int a -> Int a
add_type(
Symbol::NUM_SHIFT_RIGHT_ZERO_FILL,
top_level_function(
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1))),
),
);
// intCast : Int a -> Int b
add_type(
Symbol::NUM_INT_CAST,
top_level_function(vec![int_type(flex(TVAR1))], Box::new(int_type(flex(TVAR2)))),
);
// rem : Int a, Int a -> Result (Int a) [ DivByZero ]*
add_type(
Symbol::NUM_REM,
@ -318,6 +384,18 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
),
);
// isMultipleOf : Int a, Int a -> Bool
add_type(
Symbol::NUM_IS_MULTIPLE_OF,
top_level_function(
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(bool_type()),
),
);
// maxI128 : I128
add_type(Symbol::NUM_MAX_I128, i128_type());
// Float module
// div : Float a, Float a -> Float a
@ -539,6 +617,36 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
top_level_function(vec![int_type(flex(TVAR1))], Box::new(str_type())),
);
// fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]*
let bad_utf8 = SolvedType::TagUnion(
vec![(
TagName::Global("BadUtf8".into()),
// vec![str_utf8_problem_type()],
vec![str_utf8_byte_problem_type(), nat_type()],
)],
Box::new(SolvedType::Wildcard),
);
add_type(
Symbol::STR_FROM_UTF8,
top_level_function(
vec![list_type(u8_type())],
Box::new(result_type(str_type(), bad_utf8)),
),
);
// toBytes : Str -> List U8
add_type(
Symbol::STR_TO_BYTES,
top_level_function(vec![str_type()], Box::new(list_type(u8_type()))),
);
// fromFloat : Float a -> Str
add_type(
Symbol::STR_FROM_FLOAT,
top_level_function(vec![float_type(flex(TVAR1))], Box::new(str_type())),
);
// List module
// get : List elem, Nat -> Result elem [ OutOfBounds ]*
@ -652,6 +760,38 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
),
);
// keepOks : List before, (before -> Result after *) -> List after
add_type(Symbol::LIST_KEEP_OKS, {
let_tvars! { star, cvar, before, after};
top_level_function(
vec![
list_type(flex(before)),
closure(
vec![flex(before)],
cvar,
Box::new(result_type(flex(after), flex(star))),
),
],
Box::new(list_type(flex(after))),
)
});
// keepOks : List before, (before -> Result * after) -> List after
add_type(Symbol::LIST_KEEP_ERRS, {
let_tvars! { star, cvar, before, after};
top_level_function(
vec![
list_type(flex(before)),
closure(
vec![flex(before)],
cvar,
Box::new(result_type(flex(star), flex(after))),
),
],
Box::new(list_type(flex(after))),
)
});
// map : List before, (before -> after) -> List after
add_type(
Symbol::LIST_MAP,
@ -664,6 +804,46 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
),
);
// mapWithIndex : List before, (Nat, before -> after) -> List after
add_type(Symbol::LIST_MAP_WITH_INDEX, {
let_tvars! { cvar, before, after};
top_level_function(
vec![
list_type(flex(before)),
closure(vec![nat_type(), flex(before)], cvar, Box::new(flex(after))),
],
Box::new(list_type(flex(after))),
)
});
// map2 : List a, List b, (a, b -> c) -> List c
add_type(Symbol::LIST_MAP2, {
let_tvars! {a, b, c, cvar};
top_level_function(
vec![
list_type(flex(a)),
list_type(flex(b)),
closure(vec![flex(a), flex(b)], cvar, Box::new(flex(c))),
],
Box::new(list_type(flex(c))),
)
});
// map3 : List a, List b, List c, (a, b, c -> d) -> List d
add_type(Symbol::LIST_MAP3, {
let_tvars! {a, b, c, d, cvar};
top_level_function(
vec![
list_type(flex(a)),
list_type(flex(b)),
list_type(flex(c)),
closure(vec![flex(a), flex(b), flex(c)], cvar, Box::new(flex(d))),
],
Box::new(list_type(flex(d))),
)
});
// append : List elem, elem -> List elem
add_type(
Symbol::LIST_APPEND,
@ -747,9 +927,9 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// empty : Dict * *
add_type(Symbol::DICT_EMPTY, dict_type(flex(TVAR1), flex(TVAR2)));
// singleton : k, v -> Dict k v
// single : k, v -> Dict k v
add_type(
Symbol::DICT_SINGLETON,
Symbol::DICT_SINGLE,
top_level_function(
vec![flex(TVAR1), flex(TVAR2)],
Box::new(dict_type(flex(TVAR1), flex(TVAR2))),
@ -819,17 +999,94 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
),
);
// Dict.union : Dict k v, Dict k v -> Dict k v
add_type(
Symbol::DICT_UNION,
top_level_function(
vec![
dict_type(flex(TVAR1), flex(TVAR2)),
dict_type(flex(TVAR1), flex(TVAR2)),
],
Box::new(dict_type(flex(TVAR1), flex(TVAR2))),
),
);
// Dict.intersection : Dict k v, Dict k v -> Dict k v
add_type(
Symbol::DICT_INTERSECTION,
top_level_function(
vec![
dict_type(flex(TVAR1), flex(TVAR2)),
dict_type(flex(TVAR1), flex(TVAR2)),
],
Box::new(dict_type(flex(TVAR1), flex(TVAR2))),
),
);
// Dict.difference : Dict k v, Dict k v -> Dict k v
add_type(
Symbol::DICT_DIFFERENCE,
top_level_function(
vec![
dict_type(flex(TVAR1), flex(TVAR2)),
dict_type(flex(TVAR1), flex(TVAR2)),
],
Box::new(dict_type(flex(TVAR1), flex(TVAR2))),
),
);
// Dict.walk : Dict k v, (k, v, accum -> accum), accum -> accum
add_type(
Symbol::DICT_WALK,
top_level_function(
vec![
dict_type(flex(TVAR1), flex(TVAR2)),
closure(
vec![flex(TVAR1), flex(TVAR2), flex(TVAR3)],
TVAR4,
Box::new(flex(TVAR3)),
),
flex(TVAR3),
],
Box::new(flex(TVAR3)),
),
);
// Set module
// empty : Set a
add_type(Symbol::SET_EMPTY, set_type(flex(TVAR1)));
// singleton : a -> Set a
// single : a -> Set a
add_type(
Symbol::SET_SINGLETON,
Symbol::SET_SINGLE,
top_level_function(vec![flex(TVAR1)], Box::new(set_type(flex(TVAR1)))),
);
// len : Set * -> Nat
add_type(
Symbol::SET_LEN,
top_level_function(vec![set_type(flex(TVAR1))], Box::new(nat_type())),
);
// toList : Set a -> List a
add_type(
Symbol::SET_TO_LIST,
top_level_function(
vec![set_type(flex(TVAR1))],
Box::new(list_type(flex(TVAR1))),
),
);
// fromList : Set a -> List a
add_type(
Symbol::SET_FROM_LIST,
top_level_function(
vec![list_type(flex(TVAR1))],
Box::new(set_type(flex(TVAR1))),
),
);
// union : Set a, Set a -> Set a
add_type(
Symbol::SET_UNION,
@ -839,18 +1096,27 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
),
);
// diff : Set a, Set a -> Set a
// difference : Set a, Set a -> Set a
add_type(
Symbol::SET_DIFF,
Symbol::SET_DIFFERENCE,
top_level_function(
vec![set_type(flex(TVAR1)), set_type(flex(TVAR1))],
Box::new(set_type(flex(TVAR1))),
),
);
// foldl : Set a, (a -> b -> b), b -> b
// intersection : Set a, Set a -> Set a
add_type(
Symbol::SET_FOLDL,
Symbol::SET_INTERSECTION,
top_level_function(
vec![set_type(flex(TVAR1)), set_type(flex(TVAR1))],
Box::new(set_type(flex(TVAR1))),
),
);
// Set.walk : Set a, (a, b -> b), b -> b
add_type(
Symbol::SET_WALK,
top_level_function(
vec![
set_type(flex(TVAR1)),
@ -877,6 +1143,14 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
),
);
add_type(
Symbol::SET_CONTAINS,
top_level_function(
vec![set_type(flex(TVAR1)), flex(TVAR1)],
Box::new(bool_type()),
),
);
// Result module
// map : Result a err, (a -> b) -> Result b err
@ -891,6 +1165,43 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
),
);
// mapErr : Result a x, (x -> y) -> Result a x
add_type(
Symbol::RESULT_MAP_ERR,
top_level_function(
vec![
result_type(flex(TVAR1), flex(TVAR3)),
closure(vec![flex(TVAR3)], TVAR4, Box::new(flex(TVAR2))),
],
Box::new(result_type(flex(TVAR1), flex(TVAR2))),
),
);
// after : Result a err, (a -> Result b err) -> Result b err
add_type(
Symbol::RESULT_AFTER,
top_level_function(
vec![
result_type(flex(TVAR1), flex(TVAR3)),
closure(
vec![flex(TVAR1)],
TVAR4,
Box::new(result_type(flex(TVAR2), flex(TVAR3))),
),
],
Box::new(result_type(flex(TVAR2), flex(TVAR3))),
),
);
// withDefault : Result a x, a -> a
add_type(
Symbol::RESULT_WITH_DEFAULT,
top_level_function(
vec![result_type(flex(TVAR1), flex(TVAR3)), flex(TVAR1)],
Box::new(flex(TVAR1)),
),
);
types
}

View File

@ -270,6 +270,12 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
int_type(star, int)
});
// maxI128 : Int
add_type(Symbol::NUM_MAX_I128, {
let_tvars! { star, int };
int_type(star, int)
});
// minInt : Int
add_type(Symbol::NUM_MIN_INT, {
let_tvars! { star, int };
@ -919,8 +925,8 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
dict_type(star, k, v)
});
// singleton : k, v -> Attr * (Dict k v)
add_type(Symbol::DICT_SINGLETON, {
// single : k, v -> Attr * (Dict k v)
add_type(Symbol::DICT_SINGLE, {
let_tvars! { star, k , v };
unique_function(vec![flex(k), flex(v)], dict_type(star, k, v))
});
@ -1006,8 +1012,8 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
set_type(star, a)
});
// singleton : a -> Set a
add_type(Symbol::SET_SINGLETON, {
// single : a -> Set a
add_type(Symbol::SET_SINGLE, {
let_tvars! { star, a };
unique_function(vec![flex(a)], set_type(star, a))
});
@ -1053,13 +1059,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// diff : Attr * (Set * a)
// , Attr * (Set * a)
// -> Attr * (Set * a)
add_type(Symbol::SET_DIFF, set_combine);
add_type(Symbol::SET_DIFFERENCE, set_combine);
// foldl : Attr (* | u) (Set (Attr u a))
// , Attr Shared (Attr u a -> b -> b)
// , b
// -> b
add_type(Symbol::SET_FOLDL, {
add_type(Symbol::SET_WALK, {
let_tvars! { star, u, a, b, closure };
unique_function(
@ -1194,6 +1200,27 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
unique_function(vec![int_type(star1, int)], str_type(star2))
});
// fromUtf8 : Attr * (List U8) -> Attr * (Result Str [ BadUtf8 Utf8Problem ]*)
let bad_utf8 = SolvedType::TagUnion(
vec![(
TagName::Global("BadUtf8".into()),
// vec![builtin_aliases::str_utf8_problem_type()],
vec![
builtin_aliases::str_utf8_byte_problem_type(),
builtin_aliases::nat_type(),
],
)],
Box::new(SolvedType::Wildcard),
);
add_type(Symbol::STR_FROM_UTF8, {
let_tvars! { star1, star2, star3, star4 };
unique_function(
vec![u8_type(star1)],
result_type(star2, str_type(star3), lift(star4, bad_utf8)),
)
});
// Result module
// map : Attr * (Result (Attr a e))
@ -1302,6 +1329,11 @@ fn int_type(u: VarId, range: VarId) -> SolvedType {
)
}
#[inline(always)]
fn u8_type(u: VarId) -> SolvedType {
SolvedType::Apply(Symbol::ATTR_ATTR, vec![flex(u), builtin_aliases::u8_type()])
}
#[inline(always)]
fn bool_type(u: VarId) -> SolvedType {
SolvedType::Apply(

View File

@ -12,6 +12,7 @@ roc_module = { path = "../module" }
roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
ven_graph = { path = "../../vendor/pathfinding" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!

File diff suppressed because it is too large Load Diff

View File

@ -56,7 +56,7 @@ pub enum Expr {
Num(Variable, i64),
// Int and Float store a variable to generate better error messages
Int(Variable, Variable, i64),
Int(Variable, Variable, i128),
Float(Variable, Variable, f64),
Str(InlinableString),
List {
@ -460,6 +460,9 @@ pub fn canonicalize_expr<'a>(
loc_ret,
)
}
ast::Expr::Backpassing(_, _, _) => {
unreachable!("Backpassing should have been desugared by now")
}
ast::Expr::Closure(loc_arg_patterns, loc_body_expr) => {
// The globally unique symbol that will refer to this closure once it gets converted
// into a top-level procedure for code gen.
@ -674,32 +677,43 @@ pub fn canonicalize_expr<'a>(
Output::default(),
)
}
ast::Expr::If(cond, then_branch, else_branch) => {
let (loc_cond, mut output) =
canonicalize_expr(env, var_store, scope, cond.region, &cond.value);
let (loc_then, then_output) = canonicalize_expr(
env,
var_store,
scope,
then_branch.region,
&then_branch.value,
);
ast::Expr::If(if_thens, final_else_branch) => {
let mut branches = Vec::with_capacity(1);
let mut output = Output::default();
for (condition, then_branch) in if_thens.iter() {
let (loc_cond, cond_output) =
canonicalize_expr(env, var_store, scope, condition.region, &condition.value);
let (loc_then, then_output) = canonicalize_expr(
env,
var_store,
scope,
then_branch.region,
&then_branch.value,
);
branches.push((loc_cond, loc_then));
output.references = output.references.union(cond_output.references);
output.references = output.references.union(then_output.references);
}
let (loc_else, else_output) = canonicalize_expr(
env,
var_store,
scope,
else_branch.region,
&else_branch.value,
final_else_branch.region,
&final_else_branch.value,
);
output.references = output.references.union(then_output.references);
output.references = output.references.union(else_output.references);
(
If {
cond_var: var_store.fresh(),
branch_var: var_store.fresh(),
branches: vec![(loc_cond, loc_then)],
branches,
final_else: Box::new(loc_else),
},
output,
@ -726,10 +740,10 @@ pub fn canonicalize_expr<'a>(
use roc_problem::can::RuntimeError::*;
(RuntimeError(MalformedClosure(region)), Output::default())
}
ast::Expr::MalformedIdent(name) => {
ast::Expr::MalformedIdent(name, bad_ident) => {
use roc_problem::can::RuntimeError::*;
let problem = MalformedIdentifier((*name).into(), region);
let problem = MalformedIdentifier((*name).into(), *bad_ident, region);
env.problem(Problem::RuntimeError(problem.clone()));
(RuntimeError(problem), Output::default())

View File

@ -122,8 +122,19 @@ where
} else {
// This is a type alias
// the should already be added to the scope when this module is canonicalized
// the symbol should already be added to the scope when this module is canonicalized
debug_assert!(scope.contains_alias(symbol));
// but now we know this symbol by a different identifier, so we still need to add it to
// the scope
match scope.import(ident, symbol, region) {
Ok(()) => {
// here we do nothing special
}
Err((_shadowed_symbol, _region)) => {
panic!("TODO gracefully handle shadowing in imports.")
}
}
}
}

View File

@ -45,7 +45,7 @@ pub fn int_expr_from_result(
) -> Expr {
// Int stores a variable to generate better error messages
match result {
Ok(int) => Expr::Int(var_store.fresh(), var_store.fresh(), int),
Ok(int) => Expr::Int(var_store.fresh(), var_store.fresh(), int.into()),
Err((raw, error)) => {
let runtime_error = InvalidInt(error, base, region, raw.into());

View File

@ -31,6 +31,31 @@ fn new_op_expr<'a>(
}
}
fn desugar_defs<'a>(
arena: &'a Bump,
region: Region,
defs: &'a [&'a Located<Def<'a>>],
loc_ret: &'a Located<Expr<'a>>,
) -> &'a Located<Expr<'a>> {
let mut desugared_defs = Vec::with_capacity_in(defs.len(), arena);
for loc_def in defs.iter() {
let loc_def = Located {
value: desugar_def(arena, &loc_def.value),
region: loc_def.region,
};
desugared_defs.push(&*arena.alloc(loc_def));
}
let desugared_defs = desugared_defs.into_bump_slice();
arena.alloc(Located {
value: Defs(desugared_defs, desugar_expr(arena, loc_ret)),
region,
})
}
pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> {
use roc_parse::ast::Def::*;
@ -88,8 +113,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
| Nested(AccessorFunction(_))
| Var { .. }
| Nested(Var { .. })
| MalformedIdent(_)
| Nested(MalformedIdent(_))
| MalformedIdent(_, _)
| Nested(MalformedIdent(_, _))
| MalformedClosure
| Nested(MalformedClosure)
| PrecedenceConflict(_, _, _, _)
@ -171,25 +196,37 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
value: Closure(loc_patterns, desugar_expr(arena, loc_ret)),
})
}
Backpassing(loc_patterns, loc_body, loc_ret)
| Nested(Backpassing(loc_patterns, loc_body, loc_ret)) => {
// loc_patterns <- loc_body
//
// loc_ret
// first desugar the body, because it may contain |>
let desugared_body = desugar_expr(arena, loc_body);
match &desugared_body.value {
Expr::Apply(function, arguments, called_via) => {
let desugared_ret = desugar_expr(arena, loc_ret);
let closure = Expr::Closure(loc_patterns, desugared_ret);
let loc_closure = Located::at_zero(closure);
let mut new_arguments: Vec<'a, &'a Located<Expr<'a>>> =
Vec::with_capacity_in(arguments.len() + 1, arena);
new_arguments.extend(arguments.iter());
new_arguments.push(arena.alloc(loc_closure));
let call = Expr::Apply(function, new_arguments.into_bump_slice(), *called_via);
let loc_call = Located::at(loc_expr.region, call);
arena.alloc(loc_call)
}
_ => panic!(),
}
}
BinOp(_) | Nested(BinOp(_)) => desugar_bin_op(arena, loc_expr),
Defs(defs, loc_ret) | Nested(Defs(defs, loc_ret)) => {
let mut desugared_defs = Vec::with_capacity_in(defs.len(), arena);
for loc_def in defs.iter() {
let loc_def = Located {
value: desugar_def(arena, &loc_def.value),
region: loc_def.region,
};
desugared_defs.push(&*arena.alloc(loc_def));
}
let desugared_defs = desugared_defs.into_bump_slice();
arena.alloc(Located {
value: Defs(desugared_defs, desugar_expr(arena, loc_ret)),
region: loc_expr.region,
})
desugar_defs(arena, loc_expr.region, *defs, loc_ret)
}
Apply(loc_fn, loc_args, called_via) | Nested(Apply(loc_fn, loc_args, called_via)) => {
let mut desugared_args = Vec::with_capacity_in(loc_args.len(), arena);
@ -290,16 +327,21 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
}),
)
}
If(condition, then_branch, else_branch)
| Nested(If(condition, then_branch, else_branch)) => {
// If does not get desugared yet so we can give more targetted error messages during
// type checking.
let desugared_cond = &*arena.alloc(desugar_expr(arena, &condition));
let desugared_then = &*arena.alloc(desugar_expr(arena, &then_branch));
let desugared_else = &*arena.alloc(desugar_expr(arena, &else_branch));
If(if_thens, final_else_branch) | Nested(If(if_thens, final_else_branch)) => {
// If does not get desugared into `when` so we can give more targetted error messages during type checking.
let desugared_final_else = &*arena.alloc(desugar_expr(arena, &final_else_branch));
let mut desugared_if_thens = Vec::with_capacity_in(if_thens.len(), arena);
for (condition, then_branch) in if_thens.iter() {
desugared_if_thens.push((
desugar_expr(arena, condition).clone(),
desugar_expr(arena, then_branch).clone(),
));
}
arena.alloc(Located {
value: If(desugared_cond, desugared_then, desugared_else),
value: If(desugared_if_thens.into_bump_slice(), desugared_final_else),
region: loc_expr.region,
})
}
@ -378,6 +420,9 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) {
And => (ModuleName::BOOL, "and"),
Or => (ModuleName::BOOL, "or"),
Pizza => unreachable!("Cannot desugar the |> operator"),
Assignment => unreachable!("Cannot desugar the = operator"),
HasType => unreachable!("Cannot desugar the : operator"),
Backpassing => unreachable!("Cannot desugar the <- operator"),
}
}

View File

@ -379,6 +379,11 @@ pub fn canonicalize_pattern<'a>(
malformed_pattern(env, problem, region)
}
MalformedIdent(_str, problem) => {
let problem = MalformedPatternProblem::BadIdent(*problem);
malformed_pattern(env, problem, region)
}
QualifiedIdentifier { .. } => {
let problem = MalformedPatternProblem::QualifiedIdentifier;
malformed_pattern(env, problem, region)

View File

@ -8,9 +8,6 @@ use roc_can::operator;
use roc_can::scope::Scope;
use roc_collections::all::MutMap;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds};
use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Parser, State, SyntaxError};
use roc_problem::can::Problem;
use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable};
@ -20,25 +17,6 @@ pub fn test_home() -> ModuleId {
ModuleIds::default().get_or_insert(&"Test".into())
}
#[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, SyntaxError<'a>> {
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
}
#[allow(dead_code)]
pub fn parse_loc_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<Located<ast::Expr<'a>>, SyntaxError<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state);
answer
.map(|(_, loc_expr, _)| loc_expr)
.map_err(|(_, fail, _)| fail)
}
#[allow(dead_code)]
pub fn can_expr(expr_str: &str) -> CanExprOut {
can_expr_with(&Bump::new(), test_home(), expr_str)
@ -56,7 +34,7 @@ pub struct CanExprOut {
#[allow(dead_code)]
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut {
let loc_expr = parse_loc_with(&arena, expr_str).unwrap_or_else(|e| {
let loc_expr = roc_parse::test_helpers::parse_loc_with(&arena, expr_str).unwrap_or_else(|e| {
panic!(
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}",
expr_str, e

View File

@ -41,7 +41,7 @@ mod test_can {
}
}
fn assert_can_int(input: &str, expected: i64) {
fn assert_can_int(input: &str, expected: i128) {
let arena = Bump::new();
let actual_out = can_expr_with(&arena, test_home(), input);
@ -139,6 +139,21 @@ mod test_can {
);
}
#[test]
fn float_double_dot() {
let string = "1.1.1";
let region = Region::zero();
assert_can(
&string.clone(),
RuntimeError(RuntimeError::InvalidFloat(
FloatErrorKind::Error,
region,
string.into(),
)),
);
}
#[test]
fn zero() {
assert_can_num("0", 0);
@ -159,6 +174,16 @@ mod test_can {
assert_can_float("-0.0", -0.0);
}
#[test]
fn scientific_positive() {
assert_can_float("5e4", 50000.0);
}
#[test]
fn scientific_negative() {
assert_can_float("5e-4", 0.0005);
}
#[test]
fn num_max() {
assert_can_num(&(i64::MAX.to_string()), i64::MAX);
@ -171,32 +196,32 @@ mod test_can {
#[test]
fn hex_max() {
assert_can_int(&format!("0x{:x}", i64::MAX), i64::MAX);
assert_can_int(&format!("0x{:x}", i64::MAX), i64::MAX.into());
}
#[test]
fn hex_min() {
assert_can_int(&format!("-0x{:x}", i64::MAX as i128 + 1), i64::MIN);
assert_can_int(&format!("-0x{:x}", i64::MAX as i128 + 1), i64::MIN.into());
}
#[test]
fn oct_max() {
assert_can_int(&format!("0o{:o}", i64::MAX), i64::MAX);
assert_can_int(&format!("0o{:o}", i64::MAX), i64::MAX.into());
}
#[test]
fn oct_min() {
assert_can_int(&format!("-0o{:o}", i64::MAX as i128 + 1), i64::MIN);
assert_can_int(&format!("-0o{:o}", i64::MAX as i128 + 1), i64::MIN.into());
}
#[test]
fn bin_max() {
assert_can_int(&format!("0b{:b}", i64::MAX), i64::MAX);
assert_can_int(&format!("0b{:b}", i64::MAX), i64::MAX.into());
}
#[test]
fn bin_min() {
assert_can_int(&format!("-0b{:b}", i64::MAX as i128 + 1), i64::MIN);
assert_can_int(&format!("-0b{:b}", i64::MAX as i128 + 1), i64::MIN.into());
}
#[test]
@ -679,21 +704,24 @@ mod test_can {
_ -> g (x - 1)
# use parens to force the ordering!
(h = \x ->
when x is
0 -> 0
_ -> g (x - 1)
(
h = \x ->
when x is
0 -> 0
_ -> g (x - 1)
(p = \x ->
when x is
0 -> 0
1 -> g (x - 1)
_ -> p (x - 1)
(
p = \x ->
when x is
0 -> 0
1 -> g (x - 1)
_ -> p (x - 1)
# variables must be (indirectly) referenced in the body for analysis to work
{ x: p, y: h }
))
# variables must be (indirectly) referenced in the body for analysis to work
{ x: p, y: h }
)
)
"#
);
let arena = Bump::new();
@ -1532,6 +1560,26 @@ mod test_can {
assert_can(r#""x\u(101010)x""#, expr_str("x\u{101010}x"));
}
#[test]
fn block_string() {
assert_can(
r#"
"""foobar"""
"#,
expr_str("foobar"),
);
assert_can(
indoc!(
r#"
"""foo
bar"""
"#
),
expr_str("foo\nbar"),
);
}
// #[test]
// fn string_with_too_large_unicode_escape() {
// // Should be too big - max size should be 10FFFF.

View File

@ -10,8 +10,6 @@ roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.2", features = ["collections"] }

View File

@ -30,7 +30,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
| Access(_, _)
| AccessorFunction(_)
| Var { .. }
| MalformedIdent(_)
| MalformedIdent(_, _)
| MalformedClosure
| GlobalTag(_)
| PrivateTag(_) => false,
@ -58,8 +58,11 @@ impl<'a> Formattable<'a> for Expr<'a> {
loc_expr.is_multiline() || args.iter().any(|loc_arg| loc_arg.is_multiline())
}
If(loc_cond, loc_if_true, loc_if_false) => {
loc_cond.is_multiline() || loc_if_true.is_multiline() || loc_if_false.is_multiline()
If(branches, final_else) => {
final_else.is_multiline()
|| branches
.iter()
.any(|(c, t)| c.is_multiline() || t.is_multiline())
}
BinOp((loc_left, _, loc_right)) => {
@ -84,6 +87,14 @@ impl<'a> Formattable<'a> for Expr<'a> {
.iter()
.any(|loc_pattern| loc_pattern.is_multiline())
}
Backpassing(loc_patterns, loc_body, loc_ret) => {
// check the body first because it's more likely to be multiline
loc_body.is_multiline()
|| loc_ret.is_multiline()
|| loc_patterns
.iter()
.any(|loc_pattern| loc_pattern.is_multiline())
}
Record { fields, .. } => fields.iter().any(|loc_field| loc_field.is_multiline()),
}
@ -238,6 +249,9 @@ impl<'a> Formattable<'a> for Expr<'a> {
Closure(loc_patterns, loc_ret) => {
fmt_closure(buf, loc_patterns, loc_ret, indent);
}
Backpassing(loc_patterns, loc_body, loc_ret) => {
fmt_backpassing(buf, loc_patterns, loc_body, loc_ret, indent);
}
Defs(defs, ret) => {
// It should theoretically be impossible to *parse* an empty defs list.
// (Canonicalization can remove defs later, but that hasn't happened yet!)
@ -257,8 +271,8 @@ impl<'a> Formattable<'a> for Expr<'a> {
// still print the return value.
ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
}
If(loc_condition, loc_then, loc_else) => {
fmt_if(buf, loc_condition, loc_then, loc_else, indent);
If(branches, final_else) => {
fmt_if(buf, branches, final_else, self.is_multiline(), indent);
}
When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent),
List {
@ -300,7 +314,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
buf.push('.');
buf.push_str(key);
}
MalformedIdent(_) => {}
MalformedIdent(_, _) => {}
MalformedClosure => {}
PrecedenceConflict(_, _, _, _) => {}
}
@ -376,6 +390,9 @@ fn fmt_bin_op<'a>(
operator::BinOp::And => buf.push_str("&&"),
operator::BinOp::Or => buf.push_str("||"),
operator::BinOp::Pizza => buf.push_str("|>"),
operator::BinOp::Assignment => unreachable!(),
operator::BinOp::HasType => unreachable!(),
operator::BinOp::Backpassing => unreachable!(),
}
buf.push(' ');
@ -399,7 +416,7 @@ fn fmt_bin_op<'a>(
}
}
pub fn fmt_list<'a>(
fn fmt_list<'a>(
buf: &mut String<'a>,
loc_items: &[&Located<Expr<'a>>],
final_comments: &'a [CommentOrNewline<'a>],
@ -483,7 +500,7 @@ pub fn fmt_list<'a>(
}
}
pub fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool {
fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool {
use roc_parse::ast::Expr::*;
match expr {
@ -629,15 +646,15 @@ fn fmt_when<'a>(
fn fmt_if<'a>(
buf: &mut String<'a>,
loc_condition: &'a Located<Expr<'a>>,
loc_then: &'a Located<Expr<'a>>,
loc_else: &'a Located<Expr<'a>>,
branches: &'a [(Located<Expr<'a>>, Located<Expr<'a>>)],
final_else: &'a Located<Expr<'a>>,
is_multiline: bool,
indent: u16,
) {
let is_multiline_then = loc_then.is_multiline();
let is_multiline_else = loc_else.is_multiline();
let is_multiline_condition = loc_condition.is_multiline();
let is_multiline = is_multiline_then || is_multiline_else || is_multiline_condition;
// let is_multiline_then = loc_then.is_multiline();
// let is_multiline_else = final_else.is_multiline();
// let is_multiline_condition = loc_condition.is_multiline();
// let is_multiline = is_multiline_then || is_multiline_else || is_multiline_condition;
let return_indent = if is_multiline {
indent + INDENT
@ -645,80 +662,89 @@ fn fmt_if<'a>(
indent
};
buf.push_str("if");
for (loc_condition, loc_then) in branches.iter() {
let is_multiline_condition = loc_condition.is_multiline();
if is_multiline_condition {
match &loc_condition.value {
Expr::SpaceBefore(expr_below, spaces_above_expr) => {
fmt_comments_only(buf, spaces_above_expr.iter(), NewlineAt::Top, return_indent);
newline(buf, return_indent);
buf.push_str("if");
match &expr_below {
Expr::SpaceAfter(expr_above, spaces_below_expr) => {
expr_above.format(buf, return_indent);
fmt_comments_only(
buf,
spaces_below_expr.iter(),
NewlineAt::Top,
return_indent,
);
newline(buf, indent);
}
if is_multiline_condition {
match &loc_condition.value {
Expr::SpaceBefore(expr_below, spaces_above_expr) => {
fmt_comments_only(buf, spaces_above_expr.iter(), NewlineAt::Top, return_indent);
newline(buf, return_indent);
_ => {
expr_below.format(buf, return_indent);
match &expr_below {
Expr::SpaceAfter(expr_above, spaces_below_expr) => {
expr_above.format(buf, return_indent);
fmt_comments_only(
buf,
spaces_below_expr.iter(),
NewlineAt::Top,
return_indent,
);
newline(buf, indent);
}
_ => {
expr_below.format(buf, return_indent);
}
}
}
}
Expr::SpaceAfter(expr_above, spaces_below_expr) => {
newline(buf, return_indent);
expr_above.format(buf, return_indent);
fmt_comments_only(buf, spaces_below_expr.iter(), NewlineAt::Top, return_indent);
newline(buf, indent);
}
Expr::SpaceAfter(expr_above, spaces_below_expr) => {
newline(buf, return_indent);
expr_above.format(buf, return_indent);
fmt_comments_only(buf, spaces_below_expr.iter(), NewlineAt::Top, return_indent);
newline(buf, indent);
}
_ => {
newline(buf, return_indent);
loc_condition.format(buf, return_indent);
newline(buf, indent);
}
}
} else {
buf.push(' ');
loc_condition.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
buf.push(' ');
}
buf.push_str("then");
if is_multiline {
match &loc_then.value {
Expr::SpaceBefore(expr_below, spaces_below) => {
// we want exactly one newline, user-inserted extra newlines are ignored.
newline(buf, return_indent);
fmt_comments_only(buf, spaces_below.iter(), NewlineAt::Bottom, return_indent);
match &expr_below {
Expr::SpaceAfter(expr_above, spaces_above) => {
expr_above.format(buf, return_indent);
fmt_comments_only(buf, spaces_above.iter(), NewlineAt::Top, return_indent);
newline(buf, indent);
}
_ => {
expr_below.format(buf, return_indent);
}
_ => {
newline(buf, return_indent);
loc_condition.format(buf, return_indent);
newline(buf, indent);
}
}
_ => {
loc_condition.format(buf, return_indent);
}
} else {
buf.push(' ');
loc_condition.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
buf.push(' ');
}
buf.push_str("then");
if is_multiline {
match &loc_then.value {
Expr::SpaceBefore(expr_below, spaces_below) => {
// we want exactly one newline, user-inserted extra newlines are ignored.
newline(buf, return_indent);
fmt_comments_only(buf, spaces_below.iter(), NewlineAt::Bottom, return_indent);
match &expr_below {
Expr::SpaceAfter(expr_above, spaces_above) => {
expr_above.format(buf, return_indent);
fmt_comments_only(
buf,
spaces_above.iter(),
NewlineAt::Top,
return_indent,
);
newline(buf, indent);
}
_ => {
expr_below.format(buf, return_indent);
}
}
}
_ => {
loc_condition.format(buf, return_indent);
}
}
} else {
buf.push_str(" ");
loc_then.format(buf, return_indent);
}
} else {
buf.push_str(" ");
loc_then.format(buf, return_indent);
}
if is_multiline {
@ -728,10 +754,10 @@ fn fmt_if<'a>(
buf.push_str(" else ");
}
loc_else.format(buf, return_indent);
final_else.format(buf, return_indent);
}
pub fn fmt_closure<'a>(
fn fmt_closure<'a>(
buf: &mut String<'a>,
loc_patterns: &'a [Located<Pattern<'a>>],
loc_ret: &'a Located<Expr<'a>>,
@ -801,7 +827,77 @@ pub fn fmt_closure<'a>(
loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent);
}
pub fn fmt_record<'a>(
fn fmt_backpassing<'a>(
buf: &mut String<'a>,
loc_patterns: &'a [Located<Pattern<'a>>],
loc_body: &'a Located<Expr<'a>>,
loc_ret: &'a Located<Expr<'a>>,
indent: u16,
) {
use self::Expr::*;
let arguments_are_multiline = loc_patterns
.iter()
.any(|loc_pattern| loc_pattern.is_multiline());
// If the arguments are multiline, go down a line and indent.
let indent = if arguments_are_multiline {
indent + INDENT
} else {
indent
};
let mut it = loc_patterns.iter().peekable();
while let Some(loc_pattern) = it.next() {
loc_pattern.format(buf, indent);
if it.peek().is_some() {
if arguments_are_multiline {
buf.push(',');
newline(buf, indent);
} else {
buf.push_str(", ");
}
}
}
if arguments_are_multiline {
newline(buf, indent);
} else {
buf.push(' ');
}
buf.push_str("<-");
let is_multiline = (&loc_ret.value).is_multiline();
// If the body is multiline, go down a line and indent.
let body_indent = if is_multiline {
indent + INDENT
} else {
indent
};
// the body of the Backpass can be on the same line, or
// on a new line. If it's on the same line, insert a space.
match &loc_ret.value {
SpaceBefore(_, _) => {
// the body starts with (first comment and then) a newline
// do nothing
}
_ => {
// add a space after the `<-`
buf.push(' ');
}
};
loc_body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent);
loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
}
fn fmt_record<'a>(
buf: &mut String<'a>,
update: Option<&'a Located<Expr<'a>>>,
loc_fields: &[Located<AssignedField<'a, Expr<'a>>>],

View File

@ -39,6 +39,7 @@ impl<'a> Formattable<'a> for Pattern<'a> {
| Pattern::StrLiteral(_)
| Pattern::Underscore(_)
| Pattern::Malformed(_)
| Pattern::MalformedIdent(_, _)
| Pattern::QualifiedIdentifier { .. } => false,
}
}
@ -157,7 +158,7 @@ impl<'a> Formattable<'a> for Pattern<'a> {
}
// Malformed
Malformed(string) => buf.push_str(string),
Malformed(string) | MalformedIdent(string, _) => buf.push_str(string),
QualifiedIdentifier { module_name, ident } => {
if !module_name.is_empty() {
buf.push_str(module_name);

View File

@ -4,8 +4,6 @@ extern crate pretty_assertions;
extern crate indoc;
extern crate bumpalo;
extern crate roc_fmt;
#[macro_use]
extern crate roc_parse;
#[cfg(test)]
mod test_fmt {
@ -14,27 +12,15 @@ mod test_fmt {
use roc_fmt::annotation::{Formattable, Newlines, Parens};
use roc_fmt::def::fmt_def;
use roc_fmt::module::fmt_module;
use roc_parse::ast::{Attempting, Expr};
use roc_parse::blankspace::space0_before;
use roc_parse::module::{self, module_defs};
use roc_parse::parser::{Parser, State, SyntaxError};
fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Expr<'a>, SyntaxError<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc!(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state);
answer
.map(|(_, loc_expr, _)| loc_expr.value)
.map_err(|(_, fail, _)| fail)
}
use roc_parse::parser::{Parser, State};
fn expr_formats_to(input: &str, expected: &str) {
let arena = Bump::new();
let input = input.trim_end();
let expected = expected.trim_end();
match parse_with(&arena, input) {
match roc_parse::test_helpers::parse_expr_with(&arena, input.trim()) {
Ok(actual) => {
let mut buf = String::new_in(&arena);
@ -55,8 +41,8 @@ mod test_fmt {
let src = src.trim_end();
let expected = expected.trim_end();
match module::header().parse(&arena, State::new_in(&arena, src.as_bytes(), Attempting::Module)) {
Ok((_, actual, state)) => {
match module::parse_header(&arena, State::new(src.as_bytes())) {
Ok((actual, state)) => {
let mut buf = String::new_in(&arena);
fmt_module(&mut buf, &actual);

View File

@ -12,7 +12,6 @@ roc_module = { path = "../module" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
roc_constrain = { path = "../constrain" }
roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
@ -40,7 +39,6 @@ either = "1.6.1"
# This way, GitHub Actions works and nobody's builds get broken.
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release3" }
target-lexicon = "0.10"
libloading = "0.6"
[dev-dependencies]
roc_can = { path = "../can" }

View File

@ -1,21 +1,385 @@
use inkwell::types::BasicTypeEnum;
use roc_module::low_level::LowLevel;
use crate::debug_info_init;
use crate::llvm::build::{set_name, Env, FAST_CALL_CONV};
use crate::llvm::convert::basic_type_from_layout;
use crate::llvm::refcounting::{decrement_refcount_layout, increment_refcount_layout, Mode};
use inkwell::attributes::{Attribute, AttributeLoc};
/// Helpers for interacting with the zig that generates bitcode
use inkwell::types::{BasicType, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue};
use inkwell::AddressSpace;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Layout, LayoutIds};
pub fn call_bitcode_fn<'a, 'ctx, 'env>(
op: LowLevel,
env: &Env<'a, 'ctx, 'env>,
args: &[BasicValueEnum<'ctx>],
fn_name: &str,
) -> BasicValueEnum<'ctx> {
call_bitcode_fn_help(env, args, fn_name)
.try_as_basic_value()
.left()
.unwrap_or_else(|| {
panic!(
"LLVM error: Did not get return value from bitcode function {:?}",
fn_name
)
})
}
pub fn call_void_bitcode_fn<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
args: &[BasicValueEnum<'ctx>],
fn_name: &str,
) -> InstructionValue<'ctx> {
call_bitcode_fn_help(env, args, fn_name)
.try_as_basic_value()
.right()
.unwrap_or_else(|| panic!("LLVM error: Tried to call void bitcode function, but got return value from bitcode function, {:?}", fn_name))
}
fn call_bitcode_fn_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
args: &[BasicValueEnum<'ctx>],
fn_name: &str,
) -> CallSiteValue<'ctx> {
let fn_val = env
.module
.get_function(fn_name)
.unwrap_or_else(|| panic!("Unrecognized builtin function: {:?} - if you're working on the Roc compiler, do you need to rebuild the bitcode? See compiler/builtins/bitcode/README.md", fn_name));
.module
.get_function(fn_name)
.unwrap_or_else(|| panic!("Unrecognized builtin function: {:?} - if you're working on the Roc compiler, do you need to rebuild the bitcode? See compiler/builtins/bitcode/README.md", fn_name));
let call = env.builder.build_call(fn_val, args, "call_builtin");
call.set_call_convention(fn_val.get_call_conventions());
call.try_as_basic_value()
.left()
.unwrap_or_else(|| panic!("LLVM error: Invalid call for low-level op {:?}", op))
call
}
const ARGUMENT_SYMBOLS: [Symbol; 8] = [
Symbol::ARG_1,
Symbol::ARG_2,
Symbol::ARG_3,
Symbol::ARG_4,
Symbol::ARG_5,
Symbol::ARG_6,
Symbol::ARG_7,
Symbol::ARG_8,
];
pub fn build_transform_caller<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
function_layout: &Layout<'a>,
argument_layouts: &[Layout<'a>],
) -> FunctionValue<'ctx> {
let symbol = Symbol::ZIG_FUNCTION_CALLER;
let fn_name = layout_ids
.get(symbol, &function_layout)
.to_symbol_string(symbol, &env.interns);
match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => build_transform_caller_help(env, function_layout, argument_layouts, &fn_name),
}
}
fn build_transform_caller_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
function_layout: &Layout<'a>,
argument_layouts: &[Layout<'a>],
fn_name: &str,
) -> FunctionValue<'ctx> {
debug_assert!(argument_layouts.len() <= 7);
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
env.context.void_type().into(),
&(bumpalo::vec![ in env.arena; BasicTypeEnum::PointerType(arg_type); argument_layouts.len() + 2 ]),
);
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let attr = env.context.create_enum_attribute(kind_id, 1);
function_value.add_attribute(AttributeLoc::Function, attr);
let entry = env.context.append_basic_block(function_value, "entry");
env.builder.position_at_end(entry);
debug_info_init!(env, function_value);
let mut it = function_value.get_param_iter();
let closure_ptr = it.next().unwrap().into_pointer_value();
set_name(closure_ptr.into(), Symbol::ARG_1.ident_string(&env.interns));
let arguments =
bumpalo::collections::Vec::from_iter_in(it.take(argument_layouts.len()), env.arena);
for (argument, name) in arguments.iter().zip(ARGUMENT_SYMBOLS[1..].iter()) {
set_name(*argument, name.ident_string(&env.interns));
}
let closure_type =
basic_type_from_layout(env.arena, env.context, function_layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic);
let mut arguments_cast =
bumpalo::collections::Vec::with_capacity_in(arguments.len(), env.arena);
for (argument_ptr, layout) in arguments.iter().zip(argument_layouts) {
let basic_type = basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic);
let argument_cast = env
.builder
.build_bitcast(*argument_ptr, basic_type, "load_opaque")
.into_pointer_value();
let argument = env.builder.build_load(argument_cast, "load_opaque");
arguments_cast.push(argument);
}
let closure_cast = env
.builder
.build_bitcast(closure_ptr, closure_type, "load_opaque")
.into_pointer_value();
let fpointer = env.builder.build_load(closure_cast, "load_opaque");
let call = match function_layout {
Layout::FunctionPointer(_, _) => env.builder.build_call(
fpointer.into_pointer_value(),
arguments_cast.as_slice(),
"tmp",
),
Layout::Closure(_, _, _) | Layout::Struct(_) => {
let pair = fpointer.into_struct_value();
let fpointer = env
.builder
.build_extract_value(pair, 0, "get_fpointer")
.unwrap();
let closure_data = env
.builder
.build_extract_value(pair, 1, "get_closure_data")
.unwrap();
arguments_cast.push(closure_data);
env.builder.build_call(
fpointer.into_pointer_value(),
arguments_cast.as_slice(),
"tmp",
)
}
_ => unreachable!("layout is not callable {:?}", function_layout),
};
call.set_call_convention(FAST_CALL_CONV);
let result = call
.try_as_basic_value()
.left()
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."));
let result_u8_ptr = function_value
.get_nth_param(argument_layouts.len() as u32 + 1)
.unwrap();
let result_ptr = env
.builder
.build_bitcast(
result_u8_ptr,
result.get_type().ptr_type(AddressSpace::Generic),
"write_result",
)
.into_pointer_value();
env.builder.build_store(result_ptr, result);
env.builder.build_return(None);
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
function_value
}
pub fn build_inc_n_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
n: u64,
) -> FunctionValue<'ctx> {
build_rc_wrapper(env, layout_ids, layout, Mode::Inc(n))
}
pub fn build_inc_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
) -> FunctionValue<'ctx> {
build_rc_wrapper(env, layout_ids, layout, Mode::Inc(1))
}
pub fn build_dec_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
) -> FunctionValue<'ctx> {
build_rc_wrapper(env, layout_ids, layout, Mode::Dec)
}
pub fn build_rc_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
rc_operation: Mode,
) -> FunctionValue<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let symbol = Symbol::GENERIC_RC_REF;
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let fn_name = match rc_operation {
Mode::Inc(n) => format!("{}_inc_{}", fn_name, n),
Mode::Dec => format!("{}_dec", fn_name),
};
let function_value = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
env.context.void_type().into(),
&[arg_type.into()],
);
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let attr = env.context.create_enum_attribute(kind_id, 1);
function_value.add_attribute(AttributeLoc::Function, attr);
let entry = env.context.append_basic_block(function_value, "entry");
env.builder.position_at_end(entry);
debug_info_init!(env, function_value);
let mut it = function_value.get_param_iter();
let value_ptr = it.next().unwrap().into_pointer_value();
set_name(value_ptr.into(), Symbol::ARG_1.ident_string(&env.interns));
let value_type = basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic);
let value_cast = env
.builder
.build_bitcast(value_ptr, value_type, "load_opaque")
.into_pointer_value();
let value = env.builder.build_load(value_cast, "load_opaque");
match rc_operation {
Mode::Inc(n) => {
increment_refcount_layout(env, function_value, layout_ids, n, value, layout);
}
Mode::Dec => {
decrement_refcount_layout(env, function_value, layout_ids, value, layout);
}
}
env.builder.build_return(None);
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
function_value
}
pub fn build_eq_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
) -> FunctionValue<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let symbol = Symbol::GENERIC_EQ_REF;
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let function_value = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
env.context.bool_type().into(),
&[arg_type.into(), arg_type.into()],
);
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let attr = env.context.create_enum_attribute(kind_id, 1);
function_value.add_attribute(AttributeLoc::Function, attr);
let entry = env.context.append_basic_block(function_value, "entry");
env.builder.position_at_end(entry);
debug_info_init!(env, function_value);
let mut it = function_value.get_param_iter();
let value_ptr1 = it.next().unwrap().into_pointer_value();
let value_ptr2 = it.next().unwrap().into_pointer_value();
set_name(value_ptr1.into(), Symbol::ARG_1.ident_string(&env.interns));
set_name(value_ptr2.into(), Symbol::ARG_2.ident_string(&env.interns));
let value_type = basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic);
let value_cast1 = env
.builder
.build_bitcast(value_ptr1, value_type, "load_opaque")
.into_pointer_value();
let value_cast2 = env
.builder
.build_bitcast(value_ptr2, value_type, "load_opaque")
.into_pointer_value();
let value1 = env.builder.build_load(value_cast1, "load_opaque");
let value2 = env.builder.build_load(value_cast2, "load_opaque");
let result =
crate::llvm::compare::generic_eq(env, layout_ids, value1, value2, layout, layout);
env.builder.build_return(Some(&result));
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
function_value
}

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,13 @@
use crate::debug_info_init;
use crate::llvm::bitcode::{
build_dec_wrapper, build_eq_wrapper, build_inc_wrapper, build_transform_caller,
call_bitcode_fn, call_void_bitcode_fn,
};
use crate::llvm::build::{
call_bitcode_fn, call_void_bitcode_fn, complex_bitcast, load_symbol, load_symbol_and_layout,
set_name, Env, Scope,
complex_bitcast, load_symbol, load_symbol_and_layout, set_name, Env, Scope,
};
use crate::llvm::convert::{self, as_const_zero, basic_type_from_layout, collection};
use crate::llvm::refcounting::{decrement_refcount_layout, increment_refcount_layout, Mode};
use crate::llvm::refcounting::Mode;
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::types::BasicType;
use inkwell::values::{BasicValueEnum, FunctionValue, StructValue};
@ -128,8 +131,8 @@ pub fn dict_insert<'a, 'ctx, 'env>(
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
let dec_key_fn = build_rc_wrapper(env, layout_ids, key_layout, Mode::Dec);
let dec_value_fn = build_rc_wrapper(env, layout_ids, value_layout, Mode::Dec);
let dec_key_fn = build_dec_wrapper(env, layout_ids, key_layout);
let dec_value_fn = build_dec_wrapper(env, layout_ids, value_layout);
call_void_bitcode_fn(
env,
@ -198,8 +201,8 @@ pub fn dict_remove<'a, 'ctx, 'env>(
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
let dec_key_fn = build_rc_wrapper(env, layout_ids, key_layout, Mode::Dec);
let dec_value_fn = build_rc_wrapper(env, layout_ids, value_layout, Mode::Dec);
let dec_key_fn = build_dec_wrapper(env, layout_ids, key_layout);
let dec_value_fn = build_dec_wrapper(env, layout_ids, value_layout);
call_void_bitcode_fn(
env,
@ -315,7 +318,7 @@ pub fn dict_get<'a, 'ctx, 'env>(
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
let inc_value_fn = build_rc_wrapper(env, layout_ids, value_layout, Mode::Inc(1));
let inc_value_fn = build_inc_wrapper(env, layout_ids, value_layout);
// { flag: bool, value: *const u8 }
let result = call_bitcode_fn(
@ -422,6 +425,7 @@ pub fn dict_elements_rc<'a, 'ctx, 'env>(
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes);
let alignment_iv = env.context.i8_type().const_int(alignment as u64, false);
use crate::llvm::bitcode::build_rc_wrapper;
let inc_key_fn = build_rc_wrapper(env, layout_ids, key_layout, rc_operation);
let inc_value_fn = build_rc_wrapper(env, layout_ids, value_layout, rc_operation);
@ -450,7 +454,7 @@ pub fn dict_keys<'a, 'ctx, 'env>(
let builder = env.builder;
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
let zig_list_type = env.module.get_struct_type("dict.RocList").unwrap();
let zig_list_type = env.module.get_struct_type("list.RocList").unwrap();
let dict_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
env.builder
@ -467,7 +471,7 @@ pub fn dict_keys<'a, 'ctx, 'env>(
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes);
let alignment_iv = env.context.i8_type().const_int(alignment as u64, false);
let inc_key_fn = build_rc_wrapper(env, layout_ids, key_layout, Mode::Inc(1));
let inc_key_fn = build_inc_wrapper(env, layout_ids, key_layout);
let list_ptr = builder.build_alloca(zig_list_type, "list_ptr");
@ -496,6 +500,277 @@ pub fn dict_keys<'a, 'ctx, 'env>(
env.builder.build_load(list_ptr, "load_keys_list")
}
#[allow(clippy::too_many_arguments)]
pub fn dict_union<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
dict1: BasicValueEnum<'ctx>,
dict2: BasicValueEnum<'ctx>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
let dict1_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
let dict2_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
env.builder.build_store(
dict1_ptr,
struct_to_zig_dict(env, dict1.into_struct_value()),
);
env.builder.build_store(
dict2_ptr,
struct_to_zig_dict(env, dict2.into_struct_value()),
);
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false);
let value_width = env
.ptr_int()
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes);
let alignment_iv = env.context.i8_type().const_int(alignment as u64, false);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
let inc_key_fn = build_inc_wrapper(env, layout_ids, key_layout);
let inc_value_fn = build_inc_wrapper(env, layout_ids, value_layout);
let output_ptr = builder.build_alloca(zig_dict_type, "output_ptr");
call_void_bitcode_fn(
env,
&[
dict1_ptr.into(),
dict2_ptr.into(),
alignment_iv.into(),
key_width.into(),
value_width.into(),
hash_fn.as_global_value().as_pointer_value().into(),
eq_fn.as_global_value().as_pointer_value().into(),
inc_key_fn.as_global_value().as_pointer_value().into(),
inc_value_fn.as_global_value().as_pointer_value().into(),
output_ptr.into(),
],
&bitcode::DICT_UNION,
);
let output_ptr = env
.builder
.build_bitcast(
output_ptr,
convert::dict(env.context, env.ptr_bytes).ptr_type(AddressSpace::Generic),
"to_roc_dict",
)
.into_pointer_value();
env.builder.build_load(output_ptr, "load_output_ptr")
}
#[allow(clippy::too_many_arguments)]
pub fn dict_difference<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
dict1: BasicValueEnum<'ctx>,
dict2: BasicValueEnum<'ctx>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
dict_intersect_or_difference(
env,
layout_ids,
dict1,
dict2,
key_layout,
value_layout,
&bitcode::DICT_DIFFERENCE,
)
}
#[allow(clippy::too_many_arguments)]
pub fn dict_intersection<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
dict1: BasicValueEnum<'ctx>,
dict2: BasicValueEnum<'ctx>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
dict_intersect_or_difference(
env,
layout_ids,
dict1,
dict2,
key_layout,
value_layout,
&bitcode::DICT_INTERSECTION,
)
}
#[allow(clippy::too_many_arguments)]
fn dict_intersect_or_difference<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
dict1: BasicValueEnum<'ctx>,
dict2: BasicValueEnum<'ctx>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
op: &str,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
let dict1_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
let dict2_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
env.builder.build_store(
dict1_ptr,
struct_to_zig_dict(env, dict1.into_struct_value()),
);
env.builder.build_store(
dict2_ptr,
struct_to_zig_dict(env, dict2.into_struct_value()),
);
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false);
let value_width = env
.ptr_int()
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes);
let alignment_iv = env.context.i8_type().const_int(alignment as u64, false);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
let dec_key_fn = build_dec_wrapper(env, layout_ids, key_layout);
let dec_value_fn = build_dec_wrapper(env, layout_ids, value_layout);
let output_ptr = builder.build_alloca(zig_dict_type, "output_ptr");
call_void_bitcode_fn(
env,
&[
dict1_ptr.into(),
dict2_ptr.into(),
alignment_iv.into(),
key_width.into(),
value_width.into(),
hash_fn.as_global_value().as_pointer_value().into(),
eq_fn.as_global_value().as_pointer_value().into(),
dec_key_fn.as_global_value().as_pointer_value().into(),
dec_value_fn.as_global_value().as_pointer_value().into(),
output_ptr.into(),
],
op,
);
let output_ptr = env
.builder
.build_bitcast(
output_ptr,
convert::dict(env.context, env.ptr_bytes).ptr_type(AddressSpace::Generic),
"to_roc_dict",
)
.into_pointer_value();
env.builder.build_load(output_ptr, "load_output_ptr")
}
#[allow(clippy::too_many_arguments)]
pub fn dict_walk<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
dict: BasicValueEnum<'ctx>,
stepper: BasicValueEnum<'ctx>,
accum: BasicValueEnum<'ctx>,
stepper_layout: &Layout<'a>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
accum_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
let dict_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
env.builder
.build_store(dict_ptr, struct_to_zig_dict(env, dict.into_struct_value()));
let stepper_ptr = builder.build_alloca(stepper.get_type(), "stepper_ptr");
env.builder.build_store(stepper_ptr, stepper);
let stepper_caller = build_transform_caller(
env,
layout_ids,
stepper_layout,
&[
key_layout.clone(),
value_layout.clone(),
accum_layout.clone(),
],
)
.as_global_value()
.as_pointer_value();
let accum_bt = basic_type_from_layout(env.arena, env.context, accum_layout, env.ptr_bytes);
let accum_ptr = builder.build_alloca(accum_bt, "accum_ptr");
env.builder.build_store(accum_ptr, accum);
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false);
let value_width = env
.ptr_int()
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false);
let accum_width = env
.ptr_int()
.const_int(accum_layout.stack_size(env.ptr_bytes) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes);
let alignment_iv = env.context.i8_type().const_int(alignment as u64, false);
let output_ptr = builder.build_alloca(accum_bt, "output_ptr");
let inc_key_fn = build_inc_wrapper(env, layout_ids, key_layout);
let inc_value_fn = build_inc_wrapper(env, layout_ids, value_layout);
call_void_bitcode_fn(
env,
&[
dict_ptr.into(),
env.builder.build_bitcast(stepper_ptr, u8_ptr, "to_opaque"),
stepper_caller.into(),
env.builder.build_bitcast(accum_ptr, u8_ptr, "to_opaque"),
alignment_iv.into(),
key_width.into(),
value_width.into(),
accum_width.into(),
inc_key_fn.as_global_value().as_pointer_value().into(),
inc_value_fn.as_global_value().as_pointer_value().into(),
env.builder.build_bitcast(output_ptr, u8_ptr, "to_opaque"),
],
&bitcode::DICT_WALK,
);
env.builder.build_load(output_ptr, "load_output_ptr")
}
#[allow(clippy::too_many_arguments)]
pub fn dict_values<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -507,7 +782,7 @@ pub fn dict_values<'a, 'ctx, 'env>(
let builder = env.builder;
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
let zig_list_type = env.module.get_struct_type("dict.RocList").unwrap();
let zig_list_type = env.module.get_struct_type("list.RocList").unwrap();
let dict_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
env.builder
@ -524,7 +799,7 @@ pub fn dict_values<'a, 'ctx, 'env>(
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes);
let alignment_iv = env.context.i8_type().const_int(alignment as u64, false);
let inc_value_fn = build_rc_wrapper(env, layout_ids, value_layout, Mode::Inc(1));
let inc_value_fn = build_inc_wrapper(env, layout_ids, value_layout);
let list_ptr = builder.build_alloca(zig_list_type, "list_ptr");
@ -553,6 +828,68 @@ pub fn dict_values<'a, 'ctx, 'env>(
env.builder.build_load(list_ptr, "load_keys_list")
}
#[allow(clippy::too_many_arguments)]
pub fn set_from_list<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
list: BasicValueEnum<'ctx>,
key_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
let list_alloca = builder.build_alloca(list.get_type(), "list_alloca");
let list_ptr = env.builder.build_bitcast(
list_alloca,
env.context.i128_type().ptr_type(AddressSpace::Generic),
"to_zig_list",
);
env.builder.build_store(list_alloca, list);
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false);
let value_width = env.ptr_int().const_zero();
let result_alloca =
builder.build_alloca(convert::dict(env.context, env.ptr_bytes), "result_alloca");
let result_ptr = builder.build_bitcast(
result_alloca,
zig_dict_type.ptr_type(AddressSpace::Generic),
"to_zig_dict",
);
let alignment =
Alignment::from_key_value_layout(key_layout, &Layout::Struct(&[]), env.ptr_bytes);
let alignment_iv = env.context.i8_type().const_int(alignment as u64, false);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
let dec_key_fn = build_dec_wrapper(env, layout_ids, key_layout);
call_void_bitcode_fn(
env,
&[
env.builder
.build_load(list_ptr.into_pointer_value(), "as_i128"),
alignment_iv.into(),
key_width.into(),
value_width.into(),
hash_fn.as_global_value().as_pointer_value().into(),
eq_fn.as_global_value().as_pointer_value().into(),
dec_key_fn.as_global_value().as_pointer_value().into(),
result_ptr,
],
&bitcode::SET_FROM_LIST,
);
env.builder.build_load(result_alloca, "load_result")
}
fn build_hash_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
@ -622,158 +959,6 @@ fn build_hash_wrapper<'a, 'ctx, 'env>(
function_value
}
fn build_eq_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
) -> FunctionValue<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let symbol = Symbol::GENERIC_EQ_REF;
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let function_value = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
env.context.bool_type().into(),
&[arg_type.into(), arg_type.into()],
);
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let attr = env.context.create_enum_attribute(kind_id, 1);
function_value.add_attribute(AttributeLoc::Function, attr);
let entry = env.context.append_basic_block(function_value, "entry");
env.builder.position_at_end(entry);
debug_info_init!(env, function_value);
let mut it = function_value.get_param_iter();
let value_ptr1 = it.next().unwrap().into_pointer_value();
let value_ptr2 = it.next().unwrap().into_pointer_value();
set_name(value_ptr1.into(), Symbol::ARG_1.ident_string(&env.interns));
set_name(value_ptr2.into(), Symbol::ARG_2.ident_string(&env.interns));
let value_type = basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic);
let value_cast1 = env
.builder
.build_bitcast(value_ptr1, value_type, "load_opaque")
.into_pointer_value();
let value_cast2 = env
.builder
.build_bitcast(value_ptr2, value_type, "load_opaque")
.into_pointer_value();
let value1 = env.builder.build_load(value_cast1, "load_opaque");
let value2 = env.builder.build_load(value_cast2, "load_opaque");
let result =
crate::llvm::compare::generic_eq(env, layout_ids, value1, value2, layout, layout);
env.builder.build_return(Some(&result));
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
function_value
}
fn build_rc_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
rc_operation: Mode,
) -> FunctionValue<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let symbol = Symbol::GENERIC_RC_REF;
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let fn_name = match rc_operation {
Mode::Inc(n) => format!("{}_inc_{}", fn_name, n),
Mode::Dec => format!("{}_dec", fn_name),
};
let function_value = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
env.context.void_type().into(),
&[arg_type.into()],
);
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let attr = env.context.create_enum_attribute(kind_id, 1);
function_value.add_attribute(AttributeLoc::Function, attr);
let entry = env.context.append_basic_block(function_value, "entry");
env.builder.position_at_end(entry);
debug_info_init!(env, function_value);
let mut it = function_value.get_param_iter();
let value_ptr = it.next().unwrap().into_pointer_value();
set_name(value_ptr.into(), Symbol::ARG_1.ident_string(&env.interns));
let value_type = basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic);
let value_cast = env
.builder
.build_bitcast(value_ptr, value_type, "load_opaque")
.into_pointer_value();
let value = env.builder.build_load(value_cast, "load_opaque");
match rc_operation {
Mode::Inc(n) => {
increment_refcount_layout(env, function_value, layout_ids, n, value, layout);
}
Mode::Dec => {
decrement_refcount_layout(env, function_value, layout_ids, value, layout);
}
}
env.builder.build_return(None);
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
function_value
}
fn dict_symbol_to_zig_dict<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,

View File

@ -1,8 +1,7 @@
use crate::debug_info_init;
use crate::llvm::bitcode::call_bitcode_fn;
use crate::llvm::build::Env;
use crate::llvm::build::{
call_bitcode_fn, cast_block_of_memory_to_tag, complex_bitcast, set_name, FAST_CALL_CONV,
};
use crate::llvm::build::{cast_block_of_memory_to_tag, complex_bitcast, set_name, FAST_CALL_CONV};
use crate::llvm::build_str;
use crate::llvm::convert::basic_type_from_layout;
use bumpalo::collections::Vec;

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,10 @@
use crate::llvm::build::{
call_bitcode_fn, call_void_bitcode_fn, complex_bitcast, Env, InPlace, Scope,
};
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
use crate::llvm::build::{complex_bitcast, Env, InPlace, Scope};
use crate::llvm::build_list::{allocate_list, store_list};
use crate::llvm::convert::collection;
use inkwell::builder::Builder;
use inkwell::types::BasicTypeEnum;
use inkwell::values::{BasicValueEnum, IntValue, PointerValue, StructValue};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::AddressSpace;
use roc_builtins::bitcode;
use roc_module::symbol::Symbol;
@ -274,6 +273,89 @@ pub fn str_from_int<'a, 'ctx, 'env>(
zig_str_to_struct(env, zig_result).into()
}
/// Str.toBytes : Str -> List U8
pub fn str_to_bytes<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
original_wrapper: StructValue<'ctx>,
) -> BasicValueEnum<'ctx> {
let string = complex_bitcast(
env.builder,
original_wrapper.into(),
env.context.i128_type().into(),
"to_bytes",
);
let zig_result = call_bitcode_fn(env, &[string], &bitcode::STR_TO_BYTES);
complex_bitcast(
env.builder,
zig_result,
collection(env.context, env.ptr_bytes).into(),
"to_bytes",
)
}
/// Str.fromUtf8 : List U8 -> { a : Bool, b : Str, c : Nat, d : I8 }
pub fn str_from_utf8<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
_parent: FunctionValue<'ctx>,
original_wrapper: StructValue<'ctx>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let ctx = env.context;
let result_type = env.module.get_struct_type("str.FromUtf8Result").unwrap();
let result_ptr = builder.build_alloca(result_type, "alloca_utf8_validate_bytes_result");
call_void_bitcode_fn(
env,
&[
complex_bitcast(
env.builder,
original_wrapper.into(),
env.context.i128_type().into(),
"to_i128",
),
result_ptr.into(),
],
&bitcode::STR_FROM_UTF8,
);
let record_type = env.context.struct_type(
&[
env.ptr_int().into(),
collection(env.context, env.ptr_bytes).into(),
env.context.bool_type().into(),
ctx.i8_type().into(),
],
false,
);
let result_ptr_cast = env
.builder
.build_bitcast(
result_ptr,
record_type.ptr_type(AddressSpace::Generic),
"to_unnamed",
)
.into_pointer_value();
builder.build_load(result_ptr_cast, "load_utf8_validate_bytes_result")
}
/// Str.fromInt : Int -> Str
pub fn str_from_float<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
int_symbol: Symbol,
) -> BasicValueEnum<'ctx> {
let float = load_symbol(scope, &int_symbol);
let zig_result = call_bitcode_fn(env, &[float], &bitcode::STR_FROM_FLOAT).into_struct_value();
zig_str_to_struct(env, zig_result).into()
}
/// Str.equal : Str, Str -> Bool
pub fn str_equal<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,

View File

@ -366,7 +366,6 @@ fn build_list_eq<'a, 'ctx, 'env>(
list2: StructValue<'ctx>,
when_recursive: WhenRecursive<'a>,
) -> BasicValueEnum<'ctx> {
dbg!("list", &when_recursive);
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();

View File

@ -283,6 +283,10 @@ pub fn dict(ctx: &Context, ptr_bytes: u32) -> StructType<'_> {
)
}
pub fn dict_ptr(ctx: &Context, ptr_bytes: u32) -> PointerType<'_> {
dict(ctx, ptr_bytes).ptr_type(AddressSpace::Generic)
}
pub fn ptr_int(ctx: &Context, ptr_bytes: u32) -> IntType<'_> {
match ptr_bytes {
1 => ctx.i8_type(),

View File

@ -1,3 +1,4 @@
pub mod bitcode;
pub mod build;
pub mod build_dict;
pub mod build_hash;

View File

@ -13,6 +13,7 @@ use inkwell::module::Linkage;
use inkwell::types::{AnyTypeEnum, BasicType, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::{AddressSpace, IntPredicate};
use roc_module::symbol::Interns;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds, MemoryMode, UnionLayout};
@ -284,6 +285,7 @@ fn modify_refcount_struct<'a, 'ctx, 'env>(
value: BasicValueEnum<'ctx>,
layouts: &[Layout<'a>],
mode: Mode,
when_recursive: &WhenRecursive<'a>,
) {
let wrapper_struct = value.into_struct_value();
@ -294,7 +296,15 @@ fn modify_refcount_struct<'a, 'ctx, 'env>(
.build_extract_value(wrapper_struct, i as u32, "decrement_struct_field")
.unwrap();
modify_refcount_layout(env, parent, layout_ids, mode, field_ptr, field_layout);
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
when_recursive,
field_ptr,
field_layout,
);
}
}
}
@ -329,9 +339,9 @@ pub fn decrement_refcount_layout<'a, 'ctx, 'env>(
fn modify_refcount_builtin<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
value: BasicValueEnum<'ctx>,
layout: &Layout<'a>,
builtin: &Builtin<'a>,
@ -341,30 +351,17 @@ fn modify_refcount_builtin<'a, 'ctx, 'env>(
match builtin {
List(memory_mode, element_layout) => {
let wrapper_struct = value.into_struct_value();
if element_layout.contains_refcounted() {
let ptr_type =
basic_type_from_layout(env.arena, env.context, element_layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic);
let (len, ptr) = load_list(env.builder, wrapper_struct, ptr_type);
let loop_fn = |_index, element| {
modify_refcount_layout(env, parent, layout_ids, mode, element, element_layout);
};
incrementing_elem_loop(
env.builder,
env.context,
parent,
ptr,
len,
"modify_rc_index",
loop_fn,
);
}
if let MemoryMode::Refcounted = memory_mode {
modify_refcount_list(env, layout_ids, mode, layout, wrapper_struct);
modify_refcount_list(
env,
layout_ids,
mode,
when_recursive,
layout,
element_layout,
wrapper_struct,
);
}
}
Set(element_layout) => {
@ -379,6 +376,7 @@ fn modify_refcount_builtin<'a, 'ctx, 'env>(
env,
layout_ids,
mode,
when_recursive,
layout,
key_layout,
value_layout,
@ -403,13 +401,45 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
mode: Mode,
value: BasicValueEnum<'ctx>,
layout: &Layout<'a>,
) {
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
&WhenRecursive::Unreachable,
value,
layout,
);
}
#[derive(Clone, Debug, PartialEq)]
enum WhenRecursive<'a> {
Unreachable,
Loop(UnionLayout<'a>),
}
fn modify_refcount_layout_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
value: BasicValueEnum<'ctx>,
layout: &Layout<'a>,
) {
use Layout::*;
match layout {
Builtin(builtin) => {
modify_refcount_builtin(env, parent, layout_ids, mode, value, layout, builtin)
}
Builtin(builtin) => modify_refcount_builtin(
env,
layout_ids,
mode,
when_recursive,
value,
layout,
builtin,
),
Union(variant) => {
use UnionLayout::*;
@ -424,6 +454,7 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
env,
layout_ids,
mode,
&WhenRecursive::Loop(variant.clone()),
tags,
value.into_pointer_value(),
true,
@ -439,6 +470,7 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
env,
layout_ids,
mode,
&WhenRecursive::Loop(variant.clone()),
&*env.arena.alloc([other_fields]),
value.into_pointer_value(),
true,
@ -452,6 +484,7 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
env,
layout_ids,
mode,
&WhenRecursive::Loop(variant.clone()),
&*env.arena.alloc([*fields]),
value.into_pointer_value(),
true,
@ -464,13 +497,16 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
env,
layout_ids,
mode,
&WhenRecursive::Loop(variant.clone()),
tags,
value.into_pointer_value(),
false,
);
}
NonRecursive(tags) => modify_refcount_union(env, layout_ids, mode, tags, value),
NonRecursive(tags) => {
modify_refcount_union(env, layout_ids, mode, when_recursive, tags, value)
}
}
}
Closure(_, closure_layout, _) => {
@ -482,11 +518,12 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
.build_extract_value(wrapper_struct, 1, "modify_rc_closure_data")
.unwrap();
modify_refcount_layout(
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
when_recursive,
field_ptr,
&closure_layout.as_block_of_memory_layout(),
)
@ -494,12 +531,45 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
}
Struct(layouts) => {
modify_refcount_struct(env, parent, layout_ids, value, layouts, mode);
modify_refcount_struct(
env,
parent,
layout_ids,
value,
layouts,
mode,
when_recursive,
);
}
PhantomEmptyStruct => {}
RecursivePointer => todo!("TODO implement decrement layout of recursive tag union"),
Layout::RecursivePointer => match when_recursive {
WhenRecursive::Unreachable => {
unreachable!("recursion pointers should never be hashed directly")
}
WhenRecursive::Loop(union_layout) => {
let layout = Layout::Union(union_layout.clone());
let bt = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes);
// cast the i64 pointer to a pointer to block of memory
let field_cast = env
.builder
.build_bitcast(value, bt, "i64_to_opaque")
.into_pointer_value();
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
when_recursive,
field_cast.into(),
&layout,
)
}
},
FunctionPointer(_, _) | Pointer(_) => {}
}
@ -509,20 +579,22 @@ fn modify_refcount_list<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
layout: &Layout<'a>,
element_layout: &Layout<'a>,
original_wrapper: StructValue<'ctx>,
) {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let (call_name, symbol) = match mode {
Mode::Inc(_) => ("increment_list", Symbol::INC),
Mode::Dec => ("decrement_list", Symbol::DEC),
};
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let (call_name, fn_name) = function_name_from_mode(
layout_ids,
&env.interns,
"increment_list",
"decrement_list",
&layout,
mode,
);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
@ -530,7 +602,15 @@ fn modify_refcount_list<'a, 'ctx, 'env>(
let basic_type = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes);
let function_value = build_header(env, basic_type, mode, &fn_name);
modify_refcount_list_help(env, mode, layout, function_value);
modify_refcount_list_help(
env,
layout_ids,
mode,
when_recursive,
layout,
element_layout,
function_value,
);
function_value
}
@ -552,8 +632,11 @@ fn mode_to_call_mode(function: FunctionValue<'_>, mode: Mode) -> CallMode<'_> {
fn modify_refcount_list_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
layout: &Layout<'a>,
element_layout: &Layout<'a>,
fn_val: FunctionValue<'ctx>,
) {
let builder = env.builder;
@ -592,6 +675,36 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
builder.position_at_end(modification_block);
if element_layout.contains_refcounted() {
let ptr_type =
basic_type_from_layout(env.arena, env.context, element_layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic);
let (len, ptr) = load_list(env.builder, original_wrapper, ptr_type);
let loop_fn = |_index, element| {
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
when_recursive,
element,
element_layout,
);
};
incrementing_elem_loop(
env.builder,
env.context,
parent,
ptr,
len,
"modify_rc_index",
loop_fn,
);
}
let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper);
let call_mode = mode_to_call_mode(fn_val, mode);
refcount_ptr.modify(call_mode, layout, env);
@ -614,14 +727,14 @@ fn modify_refcount_str<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let (call_name, symbol) = match mode {
Mode::Inc(_) => ("increment_str", Symbol::INC),
Mode::Dec => ("decrement_str", Symbol::DEC),
};
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let (call_name, fn_name) = function_name_from_mode(
layout_ids,
&env.interns,
"increment_str",
"decrement_str",
&layout,
mode,
);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
@ -700,10 +813,12 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>(
builder.build_return(None);
}
#[allow(clippy::too_many_arguments)]
fn modify_refcount_dict<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
layout: &Layout<'a>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
@ -712,14 +827,14 @@ fn modify_refcount_dict<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let (call_name, symbol) = match mode {
Mode::Inc(_) => ("increment_str", Symbol::INC),
Mode::Dec => ("decrement_str", Symbol::DEC),
};
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let (call_name, fn_name) = function_name_from_mode(
layout_ids,
&env.interns,
"increment_dict",
"decrement_dict",
&layout,
mode,
);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
@ -731,6 +846,7 @@ fn modify_refcount_dict<'a, 'ctx, 'env>(
env,
layout_ids,
mode,
when_recursive,
layout,
key_layout,
value_layout,
@ -748,15 +864,23 @@ fn modify_refcount_dict<'a, 'ctx, 'env>(
call_help(env, function, mode, original_wrapper.into(), call_name);
}
#[allow(clippy::too_many_arguments)]
fn modify_refcount_dict_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
layout: &Layout<'a>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
fn_val: FunctionValue<'ctx>,
) {
debug_assert_eq!(
when_recursive,
&WhenRecursive::Unreachable,
"TODO pipe when_recursive through the dict key/value inc/dec"
);
let builder = env.builder;
let ctx = env.context;
@ -783,7 +907,7 @@ fn modify_refcount_dict_help<'a, 'ctx, 'env>(
.into_int_value();
// the block we'll always jump to when we're done
let cont_block = ctx.append_basic_block(parent, "modify_rc_str_cont");
let cont_block = ctx.append_basic_block(parent, "modify_rc_dict_cont");
let modification_block = ctx.append_basic_block(parent, "modify_rc");
let is_non_empty = builder.build_int_compare(
@ -893,20 +1017,21 @@ fn build_rec_union<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
fields: &'a [&'a [Layout<'a>]],
value: PointerValue<'ctx>,
is_nullable: bool,
) {
let layout = Layout::Union(UnionLayout::Recursive(fields));
let (call_name, symbol) = match mode {
Mode::Inc(_) => ("increment_rec_union", Symbol::INC),
Mode::Dec => ("decrement_rec_union", Symbol::DEC),
};
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let (call_name, fn_name) = function_name_from_mode(
layout_ids,
&env.interns,
"increment_rec_union",
"decrement_rec_union",
&layout,
mode,
);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
@ -919,7 +1044,15 @@ fn build_rec_union<'a, 'ctx, 'env>(
.into();
let function_value = build_header(env, basic_type, mode, &fn_name);
build_rec_union_help(env, layout_ids, mode, fields, function_value, is_nullable);
build_rec_union_help(
env,
layout_ids,
mode,
when_recursive,
fields,
function_value,
is_nullable,
);
env.builder.position_at_end(block);
env.builder
@ -936,6 +1069,7 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
tags: &[&[Layout<'a>]],
fn_val: FunctionValue<'ctx>,
is_nullable: bool,
@ -1092,7 +1226,15 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
refcount_ptr.modify(call_mode, &layout, env);
for (field, field_layout) in deferred_nonrec {
modify_refcount_layout(env, parent, layout_ids, mode, field, field_layout);
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
when_recursive,
field,
field_layout,
);
}
let call_name = pick("recursive_tag_increment", "recursive_tag_decrement");
@ -1183,10 +1325,31 @@ fn call_help<'a, 'ctx, 'env>(
call
}
fn function_name_from_mode<'a>(
layout_ids: &mut LayoutIds<'a>,
interns: &Interns,
if_inc: &'static str,
if_dec: &'static str,
layout: &Layout<'a>,
mode: Mode,
) -> (&'static str, String) {
// NOTE this is not a typo, we always determine the layout ID
// using the DEC symbol. Anything that is incrementing must also be
// decremented, so `dec` is used on more layouts. That can cause the
// layout ids of the inc and dec versions to be different, which is
// rather confusing, so now `inc_x` always corresponds to `dec_x`
let layout_id = layout_ids.get(Symbol::DEC, layout);
match mode {
Mode::Inc(_) => (if_inc, layout_id.to_symbol_string(Symbol::INC, interns)),
Mode::Dec => (if_dec, layout_id.to_symbol_string(Symbol::DEC, interns)),
}
}
fn modify_refcount_union<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
fields: &'a [&'a [Layout<'a>]],
value: BasicValueEnum<'ctx>,
) {
@ -1195,14 +1358,14 @@ fn modify_refcount_union<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let (call_name, symbol) = match mode {
Mode::Inc(_) => ("increment_union", Symbol::INC),
Mode::Dec => ("decrement_union", Symbol::DEC),
};
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let (call_name, fn_name) = function_name_from_mode(
layout_ids,
&env.interns,
"increment_union",
"decrement_union",
&layout,
mode,
);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
@ -1210,7 +1373,14 @@ fn modify_refcount_union<'a, 'ctx, 'env>(
let basic_type = block_of_memory(env.context, &layout, env.ptr_bytes);
let function_value = build_header(env, basic_type, mode, &fn_name);
modify_refcount_union_help(env, layout_ids, mode, fields, function_value);
modify_refcount_union_help(
env,
layout_ids,
mode,
when_recursive,
fields,
function_value,
);
function_value
}
@ -1227,6 +1397,7 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
tags: &[&[Layout<'a>]],
fn_val: FunctionValue<'ctx>,
) {
@ -1311,7 +1482,15 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
.build_extract_value(wrapper_struct, i as u32, "modify_tag_field")
.unwrap();
modify_refcount_layout(env, parent, layout_ids, mode, field_ptr, field_layout);
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
when_recursive,
field_ptr,
field_layout,
);
}
}

View File

@ -1,308 +0,0 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate inkwell;
extern crate libc;
extern crate roc_gen;
#[macro_use]
mod helpers;
#[cfg(test)]
mod gen_dict {
use roc_std::RocStr;
#[test]
fn dict_empty_len() {
assert_evals_to!(
indoc!(
r#"
Dict.len Dict.empty
"#
),
0,
usize
);
}
#[test]
fn dict_insert_empty() {
assert_evals_to!(
indoc!(
r#"
Dict.insert Dict.empty 42 32
|> Dict.len
"#
),
1,
usize
);
}
#[test]
fn dict_empty_contains() {
assert_evals_to!(
indoc!(
r#"
empty : Dict I64 F64
empty = Dict.empty
Dict.contains empty 42
"#
),
false,
bool
);
}
#[test]
fn dict_nonempty_contains() {
assert_evals_to!(
indoc!(
r#"
empty : Dict I64 F64
empty = Dict.insert Dict.empty 42 3.14
Dict.contains empty 42
"#
),
true,
bool
);
}
#[test]
fn dict_empty_remove() {
assert_evals_to!(
indoc!(
r#"
empty : Dict I64 F64
empty = Dict.empty
empty
|> Dict.remove 42
|> Dict.len
"#
),
0,
i64
);
}
#[test]
fn dict_nonempty_remove() {
assert_evals_to!(
indoc!(
r#"
empty : Dict I64 F64
empty = Dict.insert Dict.empty 42 3.14
empty
|> Dict.remove 42
|> Dict.len
"#
),
0,
i64
);
}
#[test]
fn dict_nonempty_get() {
assert_evals_to!(
indoc!(
r#"
empty : Dict I64 F64
empty = Dict.insert Dict.empty 42 3.14
withDefault = \x, def ->
when x is
Ok v -> v
Err _ -> def
empty
|> Dict.insert 42 3.14
|> Dict.get 42
|> withDefault 0
"#
),
3.14,
f64
);
assert_evals_to!(
indoc!(
r#"
withDefault = \x, def ->
when x is
Ok v -> v
Err _ -> def
Dict.empty
|> Dict.insert 42 3.14
|> Dict.get 43
|> withDefault 0
"#
),
0.0,
f64
);
}
#[test]
fn keys() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 I64
myDict =
Dict.empty
|> Dict.insert 0 100
|> Dict.insert 1 100
|> Dict.insert 2 100
Dict.keys myDict
"#
),
&[0, 1, 2],
&[i64]
);
}
#[test]
fn values() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 I64
myDict =
Dict.empty
|> Dict.insert 0 100
|> Dict.insert 1 200
|> Dict.insert 2 300
Dict.values myDict
"#
),
&[100, 200, 300],
&[i64]
);
}
#[test]
fn from_list_with_fold() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 I64
myDict =
[1,2,3]
|> List.walk (\value, accum -> Dict.insert accum value value) Dict.empty
Dict.values myDict
"#
),
&[2, 3, 1],
&[i64]
);
assert_evals_to!(
indoc!(
r#"
range : I64, I64, List I64-> List I64
range = \low, high, accum ->
if low < high then
range (low + 1) high (List.append accum low)
else
accum
myDict : Dict I64 I64
myDict =
# 25 elements (8 + 16 + 1) is guaranteed to overflow/reallocate at least twice
range 0 25 []
|> List.walk (\value, accum -> Dict.insert accum value value) Dict.empty
Dict.values myDict
|> List.len
"#
),
25,
i64
);
}
#[test]
fn small_str_keys() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict Str I64
myDict =
Dict.empty
|> Dict.insert "a" 100
|> Dict.insert "b" 100
|> Dict.insert "c" 100
Dict.keys myDict
"#
),
&[RocStr::from("c"), RocStr::from("a"), RocStr::from("b"),],
&[RocStr]
);
}
#[test]
fn big_str_keys() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict Str I64
myDict =
Dict.empty
|> Dict.insert "Leverage agile frameworks to provide a robust" 100
|> Dict.insert "synopsis for high level overviews. Iterative approaches" 200
|> Dict.insert "to corporate strategy foster collaborative thinking to" 300
Dict.keys myDict
"#
),
&[
RocStr::from("Leverage agile frameworks to provide a robust"),
RocStr::from("to corporate strategy foster collaborative thinking to"),
RocStr::from("synopsis for high level overviews. Iterative approaches"),
],
&[RocStr]
);
}
#[test]
fn big_str_values() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 Str
myDict =
Dict.empty
|> Dict.insert 100 "Leverage agile frameworks to provide a robust"
|> Dict.insert 200 "synopsis for high level overviews. Iterative approaches"
|> Dict.insert 300 "to corporate strategy foster collaborative thinking to"
Dict.values myDict
"#
),
&[
RocStr::from("Leverage agile frameworks to provide a robust"),
RocStr::from("to corporate strategy foster collaborative thinking to"),
RocStr::from("synopsis for high level overviews. Iterative approaches"),
],
&[RocStr]
);
}
}

View File

@ -1,157 +0,0 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate inkwell;
extern crate libc;
extern crate roc_gen;
#[macro_use]
mod helpers;
#[cfg(test)]
mod gen_hash {
#[test]
fn basic_hash() {
assert_evals_to!(
indoc!(
r#"
Dict.hashTestOnly 0 0
"#
),
9718519427346233646,
u64
);
}
#[test]
fn hash_str_with_seed() {
assert_evals_to!("Dict.hashTestOnly 1 \"a\"", 0xbed235177f41d328, u64);
assert_evals_to!("Dict.hashTestOnly 2 \"abc\"", 0xbe348debe59b27c3, u64);
}
#[test]
fn hash_record() {
assert_evals_to!("Dict.hashTestOnly 1 { x: \"a\" } ", 0xbed235177f41d328, u64);
assert_evals_to!(
"Dict.hashTestOnly 1 { x: 42, y: 3.14 } ",
5348189196103430707,
u64
);
}
#[test]
fn hash_result() {
assert_evals_to!(
"Dict.hashTestOnly 0 (List.get [ 0x1 ] 0) ",
2878521786781103245,
u64
);
}
#[test]
fn hash_linked_list() {
assert_evals_to!(
indoc!(
r#"
LinkedList a : [ Nil, Cons a (LinkedList a) ]
input : LinkedList I64
input = Nil
Dict.hashTestOnly 0 input
"#
),
0,
u64
);
assert_evals_to!(
indoc!(
r#"
LinkedList a : [ Nil, Cons a (LinkedList a) ]
input : LinkedList I64
input = Cons 4 (Cons 3 Nil)
Dict.hashTestOnly 0 input
"#
),
8287696503006938486,
u64
);
}
#[test]
fn hash_expr() {
assert_evals_to!(
indoc!(
r#"
Expr : [ Add Expr Expr, Mul Expr Expr, Val I64, Var I64 ]
x : Expr
x = Val 1
Dict.hashTestOnly 0 (Add x x)
"#
),
18264046914072177411,
u64
);
}
#[test]
fn hash_nullable_expr() {
assert_evals_to!(
indoc!(
r#"
Expr : [ Add Expr Expr, Mul Expr Expr, Val I64, Empty ]
x : Expr
x = Val 1
Dict.hashTestOnly 0 (Add x x)
"#
),
11103255846683455235,
u64
);
}
#[test]
fn hash_rosetree() {
assert_evals_to!(
indoc!(
r#"
Rose a : [ Rose (List (Rose a)) ]
x : Rose I64
x = Rose []
Dict.hashTestOnly 0 x
"#
),
0,
u64
);
}
#[test]
fn hash_list() {
assert_evals_to!(
indoc!(
r#"
x : List Str
x = [ "foo", "bar", "baz" ]
Dict.hashTestOnly 0 x
"#
),
10731521034618280801,
u64
);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,896 +0,0 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate inkwell;
extern crate libc;
extern crate roc_gen;
#[macro_use]
mod helpers;
#[cfg(test)]
mod gen_records {
#[test]
fn basic_record() {
assert_evals_to!(
indoc!(
r#"
{ y: 17, x: 15, z: 19 }.x
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: 17, z: 19 }.y
"#
),
17,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: 17, z: 19 }.z
"#
),
19,
i64
);
}
#[test]
fn f64_record() {
assert_evals_to!(
indoc!(
r#"
rec = { y: 17.2, x: 15.1, z: 19.3 }
rec.x
"#
),
15.1,
f64
);
assert_evals_to!(
indoc!(
r#"
rec = { y: 17.2, x: 15.1, z: 19.3 }
rec.y
"#
),
17.2,
f64
);
assert_evals_to!(
indoc!(
r#"
rec = { y: 17.2, x: 15.1, z: 19.3 }
rec.z
"#
),
19.3,
f64
);
}
#[test]
fn fn_record() {
assert_evals_to!(
indoc!(
r#"
getRec = \x -> { y: 17, x, z: 19 }
(getRec 15).x
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
rec = { x: 15, y: 17, z: 19 }
rec.y
"#
),
17,
i64
);
assert_evals_to!(
indoc!(
r#"
rec = { x: 15, y: 17, z: 19 }
rec.z
"#
),
19,
i64
);
assert_evals_to!(
indoc!(
r#"
rec = { x: 15, y: 17, z: 19 }
rec.z + rec.x
"#
),
34,
i64
);
}
#[test]
fn def_record() {
assert_evals_to!(
indoc!(
r#"
rec = { y: 17, x: 15, z: 19 }
rec.x
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
rec = { x: 15, y: 17, z: 19 }
rec.y
"#
),
17,
i64
);
assert_evals_to!(
indoc!(
r#"
rec = { x: 15, y: 17, z: 19 }
rec.z
"#
),
19,
i64
);
}
#[test]
fn when_on_record() {
assert_evals_to!(
indoc!(
r#"
when { x: 0x2 } is
{ x } -> x + 3
"#
),
5,
i64
);
}
#[test]
fn when_record_with_guard_pattern() {
assert_evals_to!(
indoc!(
r#"
when { x: 0x2, y: 3.14 } is
{ x: var } -> var + 3
"#
),
5,
i64
);
}
#[test]
fn let_with_record_pattern() {
assert_evals_to!(
indoc!(
r#"
{ x } = { x: 0x2, y: 3.14 }
x
"#
),
2,
i64
);
}
#[test]
fn record_guard_pattern() {
assert_evals_to!(
indoc!(
r#"
when { x: 0x2, y: 3.14 } is
{ x: 0x4 } -> 5
{ x } -> x + 3
"#
),
5,
i64
);
}
#[test]
fn twice_record_access() {
assert_evals_to!(
indoc!(
r#"
x = {a: 0x2, b: 0x3 }
x.a + x.b
"#
),
5,
i64
);
}
#[test]
fn empty_record() {
assert_evals_to!(
indoc!(
r#"
v = {}
v
"#
),
(),
()
);
}
#[test]
fn i64_record2_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3, y: 5 }
"#
),
(3, 5),
(i64, i64)
);
}
// #[test]
// fn i64_record3_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3, y: 5, z: 17 }
// "#
// ),
// (3, 5, 17),
// (i64, i64, i64)
// );
// }
#[test]
fn f64_record2_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3.1, y: 5.1 }
"#
),
(3.1, 5.1),
(f64, f64)
);
}
// #[test]
// fn f64_record3_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3.1, y: 5.1, z: 17.1 }
// "#
// ),
// (3.1, 5.1, 17.1),
// (f64, f64, f64)
// );
// }
// #[test]
// fn bool_record4_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// record : { a : Bool, b : Bool, c : Bool, d : Bool }
// record = { a: True, b: True, c : True, d : Bool }
// record
// "#
// ),
// (true, false, false, true),
// (bool, bool, bool, bool)
// );
// }
#[test]
fn i64_record1_literal() {
assert_evals_to!(
indoc!(
r#"
{ a: 3 }
"#
),
3,
i64
);
}
// #[test]
// fn i64_record9_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 }
// "#
// ),
// (3, 5, 17, 1, 9, 12, 13, 14, 15),
// (i64, i64, i64, i64, i64, i64, i64, i64, i64)
// );
// }
// #[test]
// fn f64_record3_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3.1, y: 5.1, z: 17.1 }
// "#
// ),
// (3.1, 5.1, 17.1),
// (f64, f64, f64)
// );
// }
#[test]
fn bool_literal() {
assert_evals_to!(
indoc!(
r#"
x : Bool
x = True
x
"#
),
true,
bool
);
}
#[test]
fn return_record() {
assert_evals_to!(
indoc!(
r#"
x = 4
y = 3
{ x, y }
"#
),
(4, 3),
(i64, i64)
);
}
#[test]
fn optional_field_when_use_default() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
f = \r ->
when r is
{ x: Blue, y ? 3 } -> y
{ x: Red, y ? 5 } -> y
main =
a = f { x: Blue, y: 7 }
b = f { x: Blue }
c = f { x: Red, y: 11 }
d = f { x: Red }
a * b * c * d
"#
),
3 * 5 * 7 * 11,
i64
);
}
#[test]
fn optional_field_when_use_default_nested() {
assert_evals_to!(
indoc!(
r#"
f = \r ->
when r is
{ x: Blue, y ? 3 } -> y
{ x: Red, y ? 5 } -> y
a = f { x: Blue, y: 7 }
b = f { x: Blue }
c = f { x: Red, y: 11 }
d = f { x: Red }
a * b * c * d
"#
),
3 * 5 * 7 * 11,
i64
);
}
#[test]
fn optional_field_when_no_use_default() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
f = \r ->
{ x ? 10, y } = r
x + y
main =
f { x: 4, y: 9 }
"#
),
13,
i64
);
}
#[test]
fn optional_field_when_no_use_default_nested() {
assert_evals_to!(
indoc!(
r#"
f = \r ->
{ x ? 10, y } = r
x + y
f { x: 4, y: 9 }
"#
),
13,
i64
);
}
#[test]
fn optional_field_let_use_default() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
f = \r ->
{ x ? 10, y } = r
x + y
main =
f { y: 9 }
"#
),
19,
i64
);
}
#[test]
fn optional_field_let_no_use_default() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
f = \r ->
{ x ? 10, y } = r
x + y
main =
f { x: 4, y: 9 }
"#
),
13,
i64
);
}
#[test]
fn optional_field_let_no_use_default_nested() {
assert_evals_to!(
indoc!(
r#"
f = \r ->
{ x ? 10, y } = r
x + y
f { x: 4, y: 9 }
"#
),
13,
i64
);
}
#[test]
fn optional_field_function_use_default() {
assert_evals_to!(
indoc!(
r#"
f = \{ x ? 10, y } -> x + y
f { y: 9 }
"#
),
19,
i64
);
}
#[test]
#[ignore]
fn optional_field_function_no_use_default() {
// blocked on https://github.com/rtfeldman/roc/issues/786
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
f = \{ x ? 10, y } -> x + y
main =
f { x: 4, y: 9 }
"#
),
13,
i64
);
}
#[test]
#[ignore]
fn optional_field_function_no_use_default_nested() {
// blocked on https://github.com/rtfeldman/roc/issues/786
assert_evals_to!(
indoc!(
r#"
f = \{ x ? 10, y } -> x + y
f { x: 4, y: 9 }
"#
),
13,
i64
);
}
#[test]
fn optional_field_singleton_record() {
assert_evals_to!(
indoc!(
r#"
when { x : 4 } is
{ x ? 3 } -> x
"#
),
4,
i64
);
}
#[test]
fn optional_field_empty_record() {
assert_evals_to!(
indoc!(
r#"
when { } is
{ x ? 3 } -> x
"#
),
3,
i64
);
}
#[test]
fn return_record_2() {
assert_evals_to!(
indoc!(
r#"
{ x: 3, y: 5 }
"#
),
[3, 5],
[i64; 2]
);
}
#[test]
fn return_record_3() {
assert_evals_to!(
indoc!(
r#"
{ x: 3, y: 5, z: 4 }
"#
),
(3, 5, 4),
(i64, i64, i64)
);
}
#[test]
fn return_record_4() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2 }
"#
),
[3, 5, 4, 2],
[i64; 4]
);
}
#[test]
fn return_record_5() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2, e: 1 }
"#
),
[3, 5, 4, 2, 1],
[i64; 5]
);
}
#[test]
fn return_record_6() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 }
"#
),
[3, 5, 4, 2, 1, 7],
[i64; 6]
);
}
#[test]
fn return_record_7() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 }
"#
),
[3, 5, 4, 2, 1, 7, 8],
[i64; 7]
);
}
#[test]
fn return_record_float_int() {
assert_evals_to!(
indoc!(
r#"
{ a: 3.14, b: 0x1 }
"#
),
(3.14, 0x1),
(f64, i64)
);
}
#[test]
fn return_record_int_float() {
assert_evals_to!(
indoc!(
r#"
{ a: 0x1, b: 3.14 }
"#
),
(0x1, 3.14),
(i64, f64)
);
}
#[test]
fn return_record_float_float() {
assert_evals_to!(
indoc!(
r#"
{ a: 6.28, b: 3.14 }
"#
),
(6.28, 3.14),
(f64, f64)
);
}
#[test]
fn return_record_float_float_float() {
assert_evals_to!(
indoc!(
r#"
{ a: 6.28, b: 3.14, c: 0.1 }
"#
),
(6.28, 3.14, 0.1),
(f64, f64, f64)
);
}
#[test]
fn return_nested_record() {
assert_evals_to!(
indoc!(
r#"
{ flag: 0x0, payload: { a: 6.28, b: 3.14, c: 0.1 } }
"#
),
(0x0, (6.28, 3.14, 0.1)),
(i64, (f64, f64, f64))
);
}
#[test]
fn accessor() {
assert_evals_to!(
indoc!(
r#"
.foo { foo: 4 } + .foo { bar: 6.28, foo: 3 }
"#
),
7,
i64
);
}
#[test]
fn accessor_single_element_record() {
assert_evals_to!(
indoc!(
r#"
.foo { foo: 4 }
"#
),
4,
i64
);
}
#[test]
fn update_record() {
assert_evals_to!(
indoc!(
r#"
rec = { foo: 42, bar: 6.28 }
{ rec & foo: rec.foo + 1 }
"#
),
(6.28, 43),
(f64, i64)
);
}
#[test]
fn update_single_element_record() {
assert_evals_to!(
indoc!(
r#"
rec = { foo: 42}
{ rec & foo: rec.foo + 1 }
"#
),
43,
i64
);
}
#[test]
fn booleans_in_record() {
assert_evals_to!(
indoc!("{ x: 1 == 1, y: 1 == 1 }"),
(true, true),
(bool, bool)
);
assert_evals_to!(
indoc!("{ x: 1 != 1, y: 1 == 1 }"),
(false, true),
(bool, bool)
);
assert_evals_to!(
indoc!("{ x: 1 == 1, y: 1 != 1 }"),
(true, false),
(bool, bool)
);
assert_evals_to!(
indoc!("{ x: 1 != 1, y: 1 != 1 }"),
(false, false),
(bool, bool)
);
}
#[test]
fn alignment_in_record() {
assert_evals_to!(
indoc!("{ c: 32, b: if True then Red else if True then Green else Blue, a: 1 == 1 }"),
(32i64, true, 2u8),
(i64, bool, u8)
);
}
#[test]
fn blue_and_present() {
assert_evals_to!(
indoc!(
r#"
f = \r ->
when r is
{ x: Blue, y ? 3 } -> y
{ x: Red, y ? 5 } -> y
f { x: Blue, y: 7 }
"#
),
7,
i64
);
}
#[test]
fn blue_and_absent() {
assert_evals_to!(
indoc!(
r#"
f = \r ->
when r is
{ x: Blue, y ? 3 } -> y
{ x: Red, y ? 5 } -> y
f { x: Blue }
"#
),
3,
i64
);
}
}

View File

@ -1,595 +0,0 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate inkwell;
extern crate libc;
extern crate roc_gen;
use roc_std::RocStr;
#[macro_use]
mod helpers;
const ROC_STR_MEM_SIZE: usize = core::mem::size_of::<RocStr>();
#[cfg(test)]
mod gen_str {
use crate::ROC_STR_MEM_SIZE;
use roc_std::RocStr;
use std::cmp::min;
fn small_str(str: &str) -> [u8; ROC_STR_MEM_SIZE] {
let mut bytes: [u8; ROC_STR_MEM_SIZE] = Default::default();
let mut index: usize = 0;
while index < ROC_STR_MEM_SIZE {
bytes[index] = 0;
index += 1;
}
let str_bytes = str.as_bytes();
let output_len: usize = min(str_bytes.len(), ROC_STR_MEM_SIZE);
index = 0;
while index < output_len {
bytes[index] = str_bytes[index];
index += 1;
}
bytes[ROC_STR_MEM_SIZE - 1] = 0b1000_0000 ^ (output_len as u8);
bytes
}
#[test]
fn str_split_bigger_delimiter_small_str() {
assert_evals_to!(
indoc!(
r#"
List.len (Str.split "hello" "JJJJ there")
"#
),
1,
i64
);
assert_evals_to!(
indoc!(
r#"
when List.first (Str.split "JJJ" "JJJJ there") is
Ok str ->
Str.countGraphemes str
_ ->
-1
"#
),
3,
i64
);
}
#[test]
fn str_split_str_concat_repeated() {
assert_evals_to!(
indoc!(
r#"
when List.first (Str.split "JJJJJ" "JJJJ there") is
Ok str ->
str
|> Str.concat str
|> Str.concat str
|> Str.concat str
|> Str.concat str
_ ->
"Not Str!"
"#
),
"JJJJJJJJJJJJJJJJJJJJJJJJJ",
&'static str
);
}
#[test]
fn str_split_small_str_bigger_delimiter() {
assert_evals_to!(
indoc!(
r#"
when
List.first
(Str.split "JJJ" "0123456789abcdefghi")
is
Ok str -> str
_ -> ""
"#
),
small_str("JJJ"),
[u8; ROC_STR_MEM_SIZE]
);
}
#[test]
fn str_split_big_str_small_delimiter() {
assert_evals_to!(
indoc!(
r#"
Str.split "01234567789abcdefghi?01234567789abcdefghi" "?"
"#
),
&["01234567789abcdefghi", "01234567789abcdefghi"],
&'static [&'static str]
);
assert_evals_to!(
indoc!(
r#"
Str.split "01234567789abcdefghi 3ch 01234567789abcdefghi" "3ch"
"#
),
&["01234567789abcdefghi ", " 01234567789abcdefghi"],
&'static [&'static str]
);
}
#[test]
fn str_split_small_str_small_delimiter() {
assert_evals_to!(
indoc!(
r#"
Str.split "J!J!J" "!"
"#
),
&[small_str("J"), small_str("J"), small_str("J")],
&'static [[u8; ROC_STR_MEM_SIZE]]
);
}
#[test]
fn str_split_bigger_delimiter_big_strs() {
assert_evals_to!(
indoc!(
r#"
Str.split
"string to split is shorter"
"than the delimiter which happens to be very very long"
"#
),
&["string to split is shorter"],
&'static [&'static str]
);
}
#[test]
fn str_split_empty_strs() {
assert_evals_to!(
indoc!(
r#"
Str.split "" ""
"#
),
&[small_str("")],
&'static [[u8; ROC_STR_MEM_SIZE]]
)
}
#[test]
fn str_split_minimal_example() {
assert_evals_to!(
indoc!(
r#"
Str.split "a," ","
"#
),
&[small_str("a"), small_str("")],
&'static [[u8; ROC_STR_MEM_SIZE]]
)
}
#[test]
fn str_split_small_str_big_delimiter() {
assert_evals_to!(
indoc!(
r#"
Str.split
"1---- ---- ---- ---- ----2---- ---- ---- ---- ----"
"---- ---- ---- ---- ----"
|> List.len
"#
),
3,
i64
);
assert_evals_to!(
indoc!(
r#"
Str.split
"1---- ---- ---- ---- ----2---- ---- ---- ---- ----"
"---- ---- ---- ---- ----"
"#
),
&[small_str("1"), small_str("2"), small_str("")],
&'static [[u8; ROC_STR_MEM_SIZE]]
);
}
#[test]
fn str_split_small_str_20_char_delimiter() {
assert_evals_to!(
indoc!(
r#"
Str.split
"3|-- -- -- -- -- -- |4|-- -- -- -- -- -- |"
"|-- -- -- -- -- -- |"
"#
),
&[small_str("3"), small_str("4"), small_str("")],
&'static [[u8; ROC_STR_MEM_SIZE]]
);
}
#[test]
fn str_concat_big_to_big() {
assert_evals_to!(
indoc!(
r#"
Str.concat
"First string that is fairly long. Longer strings make for different errors. "
"Second string that is also fairly long. Two long strings test things that might not appear with short strings."
"#
),
"First string that is fairly long. Longer strings make for different errors. Second string that is also fairly long. Two long strings test things that might not appear with short strings.",
&'static str
);
}
#[test]
fn small_str_literal() {
assert_evals_to!(
"\"JJJJJJJJJJJJJJJ\"",
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
#[test]
fn small_str_zeroed_literal() {
// Verifies that we zero out unused bytes in the string.
// This is important so that string equality tests don't randomly
// fail due to unused memory being there!
assert_evals_to!(
"\"J\"",
[
0x4a,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0b1000_0001
],
[u8; 16]
);
}
#[test]
fn small_str_concat_empty_first_arg() {
assert_evals_to!(
r#"Str.concat "" "JJJJJJJJJJJJJJJ""#,
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
#[test]
fn small_str_concat_empty_second_arg() {
assert_evals_to!(
r#"Str.concat "JJJJJJJJJJJJJJJ" """#,
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
#[test]
fn small_str_concat_small_to_big() {
assert_evals_to!(
r#"Str.concat "abc" " this is longer than 15 chars""#,
"abc this is longer than 15 chars",
&'static str
);
}
#[test]
fn small_str_concat_small_to_small_staying_small() {
assert_evals_to!(
r#"Str.concat "J" "JJJJJJJJJJJJJJ""#,
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
#[test]
fn small_str_concat_small_to_small_overflow_to_big() {
assert_evals_to!(
r#"Str.concat "abcdefghijklm" "nopqrstuvwxyz""#,
"abcdefghijklmnopqrstuvwxyz",
&'static str
);
}
#[test]
fn str_concat_empty() {
assert_evals_to!(r#"Str.concat "" """#, "", &'static str);
}
#[test]
fn small_str_is_empty() {
assert_evals_to!(r#"Str.isEmpty "abc""#, false, bool);
}
#[test]
fn big_str_is_empty() {
assert_evals_to!(
r#"Str.isEmpty "this is more than 15 chars long""#,
false,
bool
);
}
#[test]
fn empty_str_is_empty() {
assert_evals_to!(r#"Str.isEmpty """#, true, bool);
}
#[test]
fn str_starts_with() {
assert_evals_to!(r#"Str.startsWith "hello world" "hell""#, true, bool);
assert_evals_to!(r#"Str.startsWith "hello world" """#, true, bool);
assert_evals_to!(r#"Str.startsWith "nope" "hello world""#, false, bool);
assert_evals_to!(r#"Str.startsWith "hell" "hello world""#, false, bool);
assert_evals_to!(r#"Str.startsWith "" "hello world""#, false, bool);
}
#[test]
fn str_ends_with() {
assert_evals_to!(r#"Str.endsWith "hello world" "world""#, true, bool);
assert_evals_to!(r#"Str.endsWith "nope" "hello world""#, false, bool);
assert_evals_to!(r#"Str.endsWith "" "hello world""#, false, bool);
}
#[test]
fn str_count_graphemes_small_str() {
assert_evals_to!(r#"Str.countGraphemes "å🤔""#, 2, usize);
}
#[test]
fn str_count_graphemes_three_js() {
assert_evals_to!(r#"Str.countGraphemes "JJJ""#, 3, usize);
}
#[test]
fn str_count_graphemes_big_str() {
assert_evals_to!(
r#"Str.countGraphemes "6🤔å🤔e¥🤔çppkd🙃1jdal🦯asdfa∆ltråø˚waia8918.,🏅jjc""#,
45,
usize
);
}
#[test]
fn str_starts_with_same_big_str() {
assert_evals_to!(
r#"Str.startsWith "123456789123456789" "123456789123456789""#,
true,
bool
);
}
#[test]
fn str_starts_with_different_big_str() {
assert_evals_to!(
r#"Str.startsWith "12345678912345678910" "123456789123456789""#,
true,
bool
);
}
#[test]
fn str_starts_with_same_small_str() {
assert_evals_to!(r#"Str.startsWith "1234" "1234""#, true, bool);
}
#[test]
fn str_starts_with_different_small_str() {
assert_evals_to!(r#"Str.startsWith "1234" "12""#, true, bool);
}
#[test]
fn str_starts_with_false_small_str() {
assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool);
}
#[test]
fn str_from_int() {
assert_evals_to!(
r#"Str.fromInt 1234"#,
roc_std::RocStr::from_slice("1234".as_bytes()),
roc_std::RocStr
);
assert_evals_to!(
r#"Str.fromInt 0"#,
roc_std::RocStr::from_slice("0".as_bytes()),
roc_std::RocStr
);
assert_evals_to!(
r#"Str.fromInt -1"#,
roc_std::RocStr::from_slice("-1".as_bytes()),
roc_std::RocStr
);
let max = format!("{}", i64::MAX);
assert_evals_to!(r#"Str.fromInt Num.maxInt"#, &max, &'static str);
let min = format!("{}", i64::MIN);
assert_evals_to!(r#"Str.fromInt Num.minInt"#, &min, &'static str);
}
#[test]
fn str_equality() {
assert_evals_to!(r#""a" == "a""#, true, bool);
assert_evals_to!(
r#""loremipsumdolarsitamet" == "loremipsumdolarsitamet""#,
true,
bool
);
assert_evals_to!(r#""a" != "b""#, true, bool);
assert_evals_to!(r#""a" == "b""#, false, bool);
}
#[test]
fn str_clone() {
use roc_std::RocStr;
let long = RocStr::from_slice("loremipsumdolarsitamet".as_bytes());
let short = RocStr::from_slice("x".as_bytes());
let empty = RocStr::from_slice("".as_bytes());
debug_assert_eq!(long.clone(), long);
debug_assert_eq!(short.clone(), short);
debug_assert_eq!(empty.clone(), empty);
}
#[test]
fn nested_recursive_literal() {
assert_evals_to!(
indoc!(
r#"
Expr : [ Add Expr Expr, Val I64, Var I64 ]
expr : Expr
expr = Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))
printExpr : Expr -> Str
printExpr = \e ->
when e is
Add a b ->
"Add ("
|> Str.concat (printExpr a)
|> Str.concat ") ("
|> Str.concat (printExpr b)
|> Str.concat ")"
Val v -> "Val " |> Str.concat (Str.fromInt v)
Var v -> "Var " |> Str.concat (Str.fromInt v)
printExpr expr
"#
),
"Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))",
&'static str
);
}
#[test]
fn str_join_comma_small() {
assert_evals_to!(
r#"Str.joinWith ["1", "2"] ", " "#,
RocStr::from("1, 2"),
RocStr
);
}
#[test]
fn str_join_comma_big() {
assert_evals_to!(
r#"Str.joinWith ["10000000", "2000000", "30000000"] ", " "#,
RocStr::from("10000000, 2000000, 30000000"),
RocStr
);
}
#[test]
fn str_join_comma_single() {
assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr);
}
}

View File

@ -1,986 +0,0 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate inkwell;
extern crate libc;
extern crate roc_gen;
#[macro_use]
mod helpers;
#[cfg(test)]
mod gen_tags {
#[test]
fn applied_tag_nothing_ir() {
assert_evals_to!(
indoc!(
r#"
Maybe a : [ Just a, Nothing ]
x : Maybe I64
x = Nothing
x
"#
),
1,
(i64, i64),
|(tag, _)| tag
);
}
#[test]
fn applied_tag_nothing() {
assert_evals_to!(
indoc!(
r#"
Maybe a : [ Just a, Nothing ]
x : Maybe I64
x = Nothing
x
"#
),
1,
(i64, i64),
|(tag, _)| tag
);
}
#[test]
fn applied_tag_just() {
assert_evals_to!(
indoc!(
r#"
Maybe a : [ Just a, Nothing ]
y : Maybe I64
y = Just 0x4
y
"#
),
(0, 0x4),
(i64, i64)
);
}
#[test]
fn applied_tag_just_ir() {
assert_evals_to!(
indoc!(
r#"
Maybe a : [ Just a, Nothing ]
y : Maybe I64
y = Just 0x4
y
"#
),
(0, 0x4),
(i64, i64)
);
}
#[test]
fn applied_tag_just_enum() {
assert_evals_to!(
indoc!(
r#"
Fruit : [ Orange, Apple, Banana ]
Maybe a : [ Just a, Nothing ]
orange : Fruit
orange = Orange
y : Maybe Fruit
y = Just orange
y
"#
),
(0, 2),
(i64, u8)
);
}
// #[test]
// fn raw_result() {
// assert_evals_to!(
// indoc!(
// r#"
// x : Result I64 I64
// x = Err 41
// x
// "#
// ),
// 0,
// i8
// );
// }
#[test]
fn true_is_true() {
assert_evals_to!(
indoc!(
r#"
bool : [True, False]
bool = True
bool
"#
),
true,
bool
);
}
#[test]
fn false_is_false() {
assert_evals_to!(
indoc!(
r#"
bool : [True, False]
bool = False
bool
"#
),
false,
bool
);
}
#[test]
fn basic_enum() {
assert_evals_to!(
indoc!(
r#"
Fruit : [ Apple, Orange, Banana ]
apple : Fruit
apple = Apple
orange : Fruit
orange = Orange
apple == orange
"#
),
false,
bool
);
}
// #[test]
// fn linked_list_empty() {
// assert_evals_to!(
// indoc!(
// r#"
// LinkedList a : [ Cons a (LinkedList a), Nil ]
//
// empty : LinkedList I64
// empty = Nil
//
// 1
// "#
// ),
// 1,
// i64
// );
// }
//
// #[test]
// fn linked_list_singleton() {
// assert_evals_to!(
// indoc!(
// r#"
// LinkedList a : [ Cons a (LinkedList a), Nil ]
//
// singleton : LinkedList I64
// singleton = Cons 0x1 Nil
//
// 1
// "#
// ),
// 1,
// i64
// );
// }
//
// #[test]
// fn linked_list_is_empty() {
// assert_evals_to!(
// indoc!(
// r#"
// LinkedList a : [ Cons a (LinkedList a), Nil ]
//
// isEmpty : LinkedList a -> Bool
// isEmpty = \list ->
// when list is
// Nil -> True
// Cons _ _ -> False
//
// isEmpty (Cons 4 Nil)
// "#
// ),
// false,
// bool
// );
// }
#[test]
fn even_odd() {
assert_evals_to!(
indoc!(
r#"
even = \n ->
when n is
0 -> True
1 -> False
_ -> odd (n - 1)
odd = \n ->
when n is
0 -> False
1 -> True
_ -> even (n - 1)
odd 5 && even 42
"#
),
true,
bool
);
}
#[test]
fn gen_literal_true() {
assert_evals_to!(
indoc!(
r#"
if True then -1 else 1
"#
),
-1,
i64
);
}
#[test]
fn gen_if_float() {
assert_evals_to!(
indoc!(
r#"
if True then -1.0 else 1.0
"#
),
-1.0,
f64
);
}
#[test]
fn when_on_nothing() {
assert_evals_to!(
indoc!(
r#"
x : [ Nothing, Just I64 ]
x = Nothing
when x is
Nothing -> 0x2
Just _ -> 0x1
"#
),
2,
i64
);
}
#[test]
fn when_on_just() {
assert_evals_to!(
indoc!(
r#"
x : [ Nothing, Just I64 ]
x = Just 41
when x is
Just v -> v + 0x1
Nothing -> 0x1
"#
),
42,
i64
);
}
#[test]
fn when_on_result() {
assert_evals_to!(
indoc!(
r#"
x : Result I64 I64
x = Err 41
when x is
Err v -> v + 1
Ok _ -> 1
"#
),
42,
i64
);
}
#[test]
fn when_on_these() {
assert_evals_to!(
indoc!(
r#"
These a b : [ This a, That b, These a b ]
x : These I64 I64
x = These 0x3 0x2
when x is
These a b -> a + b
That v -> v
This v -> v
"#
),
5,
i64
);
}
#[test]
fn match_on_two_values() {
// this will produce a Chain internally
assert_evals_to!(
indoc!(
r#"
when Pair 2 3 is
Pair 4 3 -> 9
Pair a b -> a + b
"#
),
5,
i64
);
}
#[test]
fn pair_with_guard_pattern() {
assert_evals_to!(
indoc!(
r#"
when Pair 2 3 is
Pair 4 _ -> 1
Pair 3 _ -> 2
Pair a b -> a + b
"#
),
5,
i64
);
}
#[test]
fn result_with_guard_pattern() {
// This test revealed an issue with hashing Test values
assert_evals_to!(
indoc!(
r#"
x : Result I64 I64
x = Ok 2
when x is
Ok 3 -> 1
Ok _ -> 2
Err _ -> 3
"#
),
2,
i64
);
}
#[test]
fn maybe_is_just() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
Maybe a : [ Just a, Nothing ]
isJust : Maybe a -> Bool
isJust = \list ->
when list is
Nothing -> False
Just _ -> True
main =
isJust (Just 42)
"#
),
true,
bool
);
}
#[test]
fn maybe_is_just_nested() {
assert_evals_to!(
indoc!(
r#"
Maybe a : [ Just a, Nothing ]
isJust : Maybe a -> Bool
isJust = \list ->
when list is
Nothing -> False
Just _ -> True
isJust (Just 42)
"#
),
true,
bool
);
}
#[test]
fn nested_pattern_match() {
assert_evals_to!(
indoc!(
r#"
Maybe a : [ Nothing, Just a ]
x : Maybe (Maybe I64)
x = Just (Just 41)
when x is
Just (Just v) -> v + 0x1
_ -> 0x1
"#
),
42,
i64
);
}
#[test]
fn if_guard_pattern_false() {
assert_evals_to!(
indoc!(
r#"
wrapper = \{} ->
when 2 is
2 if False -> 0
_ -> 42
wrapper {}
"#
),
42,
i64
);
}
#[test]
fn if_guard_pattern_true() {
assert_evals_to!(
indoc!(
r#"
wrapper = \{} ->
when 2 is
2 if True -> 42
_ -> 0
wrapper {}
"#
),
42,
i64
);
}
#[test]
fn if_guard_exhaustiveness() {
assert_evals_to!(
indoc!(
r#"
wrapper = \{} ->
when 2 is
_ if False -> 0
_ -> 42
wrapper {}
"#
),
42,
i64
);
}
#[test]
fn when_on_enum() {
assert_evals_to!(
indoc!(
r#"
Fruit : [ Apple, Orange, Banana ]
apple : Fruit
apple = Apple
when apple is
Apple -> 1
Banana -> 2
Orange -> 3
"#
),
1,
i64
);
}
#[test]
fn pattern_matching_unit() {
assert_evals_to!(
indoc!(
r#"
Unit : [ Unit ]
f : Unit -> I64
f = \Unit -> 42
f Unit
"#
),
42,
i64
);
assert_evals_to!(
indoc!(
r#"
Unit : [ Unit ]
x : Unit
x = Unit
when x is
Unit -> 42
"#
),
42,
i64
);
assert_evals_to!(
indoc!(
r#"
f : {} -> I64
f = \{} -> 42
f {}
"#
),
42,
i64
);
assert_evals_to!(
indoc!(
r#"
when {} is
{} -> 42
"#
),
42,
i64
);
}
#[test]
fn one_element_tag() {
assert_evals_to!(
indoc!(
r#"
x : [ Pair I64 ]
x = Pair 2
x
"#
),
2,
i64
);
}
#[test]
fn nested_tag_union() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
Maybe a : [ Nothing, Just a ]
x : Maybe (Maybe I64)
x = Just (Just 41)
main =
x
"#
),
(0, (0, 41)),
(i64, (i64, i64))
);
}
#[test]
fn unit_type() {
assert_evals_to!(
indoc!(
r#"
Unit : [ Unit ]
v : Unit
v = Unit
v
"#
),
(),
()
);
}
#[test]
fn nested_record_load() {
assert_evals_to!(
indoc!(
r#"
x = { a : { b : 0x5 } }
y = x.a
y.b
"#
),
5,
i64
);
}
#[test]
fn join_point_if() {
assert_evals_to!(
indoc!(
r#"
x =
if True then 1 else 2
x
"#
),
1,
i64
);
}
#[test]
fn join_point_when() {
assert_evals_to!(
indoc!(
r#"
wrapper = \{} ->
x : [ Red, White, Blue ]
x = Blue
y =
when x is
Red -> 1
White -> 2
Blue -> 3.1
y
wrapper {}
"#
),
3.1,
f64
);
}
#[test]
fn join_point_with_cond_expr() {
assert_evals_to!(
indoc!(
r#"
wrapper = \{} ->
y =
when 1 + 2 is
3 -> 3
1 -> 1
_ -> 0
y
wrapper {}
"#
),
3,
i64
);
assert_evals_to!(
indoc!(
r#"
y =
if 1 + 2 > 0 then
3
else
0
y
"#
),
3,
i64
);
}
#[test]
fn alignment_in_single_tag_construction() {
assert_evals_to!(indoc!("Three (1 == 1) 32"), (32i64, true), (i64, bool));
assert_evals_to!(
indoc!("Three (1 == 1) (if True then Red else if True then Green else Blue) 32"),
(32i64, true, 2u8),
(i64, bool, u8)
);
}
#[test]
fn alignment_in_single_tag_pattern_match() {
assert_evals_to!(
indoc!(
r"#
x = Three (1 == 1) 32
when x is
Three bool int ->
{ bool, int }
#"
),
(32i64, true),
(i64, bool)
);
assert_evals_to!(
indoc!(
r"#
x = Three (1 == 1) (if True then Red else if True then Green else Blue) 32
when x is
Three bool color int ->
{ bool, color, int }
#"
),
(32i64, true, 2u8),
(i64, bool, u8)
);
}
#[test]
fn alignment_in_multi_tag_construction() {
assert_evals_to!(
indoc!(
r"#
x : [ Three Bool I64, Empty ]
x = Three (1 == 1) 32
x
#"
),
(1, 32i64, true),
(i64, i64, bool)
);
assert_evals_to!(
indoc!(
r"#
x : [ Three Bool [ Red, Green, Blue ] I64, Empty ]
x = Three (1 == 1) (if True then Red else if True then Green else Blue) 32
x
#"
),
(1, 32i64, true, 2u8),
(i64, i64, bool, u8)
);
}
#[test]
fn alignment_in_multi_tag_pattern_match() {
assert_evals_to!(
indoc!(
r"#
x : [ Three Bool I64, Empty ]
x = Three (1 == 1) 32
when x is
Three bool int ->
{ bool, int }
Empty ->
{ bool: False, int: 0 }
#"
),
(32i64, true),
(i64, bool)
);
assert_evals_to!(
indoc!(
r"#
x : [ Three Bool [ Red, Green, Blue ] I64, Empty ]
x = Three (1 == 1) (if True then Red else if True then Green else Blue) 32
when x is
Three bool color int ->
{ bool, color, int }
Empty ->
{ bool: False, color: Red, int: 0 }
#"
),
(32i64, true, 2u8),
(i64, bool, u8)
);
}
#[test]
#[ignore]
fn phantom_polymorphic() {
// see https://github.com/rtfeldman/roc/issues/786 and below
assert_evals_to!(
indoc!(
r"#
Point coordinate : [ Point coordinate I64 I64 ]
World : [ @World ]
zero : Point World
zero = Point @World 0 0
add : Point a -> Point a
add = \(Point c x y) -> (Point c x y)
add zero
#"
),
(0, 0),
(i64, i64)
);
}
#[test]
#[ignore]
fn phantom_polymorphic_record() {
// see https://github.com/rtfeldman/roc/issues/786
// also seemed to hit an issue where we check whether `add`
// has a Closure layout while the type is not fully specialized yet
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
Point coordinate : { coordinate : coordinate, x : I64, y : I64 }
zero : Point I64
zero = { coordinate : 0, x : 0, y : 0 }
add : Point a -> Point a
add = \{ coordinate } -> { coordinate, x: 0 + 0, y: 0 }
main = add zero
"#
),
(0, 0),
(i64, i64)
);
}
#[test]
fn result_never() {
assert_evals_to!(
indoc!(
r"#
res : Result I64 []
res = Ok 4
# we should provide this in the stdlib
never : [] -> a
when res is
Ok v -> v
Err empty -> never empty
#"
),
4,
i64
);
}
#[test]
fn nested_recursive_literal() {
assert_evals_to!(
indoc!(
r"#
Expr : [ Add Expr Expr, Val I64, Var I64 ]
e : Expr
e = Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))
e
#"
),
0,
&i64,
|x: &i64| *x
);
}
#[test]
fn newtype_wrapper() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
ConsList a : [ Nil, Cons a (ConsList a) ]
foo : ConsList I64 -> ConsList I64
foo = \t ->
when Delmin (Del t 0.0) is
Delmin (Del ry _) -> Cons 42 ry
main = foo Nil
"#
),
42,
&i64,
|x: &i64| *x
);
}
}

View File

@ -520,7 +520,7 @@ impl<
Literal::Int(x) => {
let reg = self.claim_general_reg(sym)?;
let val = *x;
ASM::mov_reg64_imm64(&mut self.buf, reg, val);
ASM::mov_reg64_imm64(&mut self.buf, reg, val as i64);
Ok(())
}
Literal::Float(x) => {

View File

@ -705,6 +705,7 @@ fn x86_64_generic_setup_stack<'a>(
}
#[inline(always)]
#[allow(clippy::unnecessary_wraps)]
fn x86_64_generic_cleanup_stack<'a>(
buf: &mut Vec<'a, u8>,
saved_regs: &[X86_64GeneralReg],
@ -845,39 +846,37 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
}
#[inline(always)]
fn mov_freg64_base32(_buf: &mut Vec<'_, u8>, _dst: X86_64FloatReg, _offset: i32) {
unimplemented!(
"loading floating point reg from base offset not yet implemented for X86_64"
);
fn mov_freg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset: i32) {
movsd_freg64_base64_offset32(buf, dst, X86_64GeneralReg::RBP, offset)
}
#[inline(always)]
fn mov_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32) {
mov_reg64_base32(buf, dst, offset);
mov_reg64_base64_offset32(buf, dst, X86_64GeneralReg::RBP, offset)
}
#[inline(always)]
fn mov_base32_freg64(_buf: &mut Vec<'_, u8>, _offset: i32, _src: X86_64FloatReg) {
unimplemented!("saving floating point reg to base offset not yet implemented for X86_64");
fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64FloatReg) {
movsd_base64_offset32_freg64(buf, X86_64GeneralReg::RBP, offset, src)
}
#[inline(always)]
fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64GeneralReg) {
mov_base32_reg64(buf, offset, src);
mov_base64_offset32_reg64(buf, X86_64GeneralReg::RBP, offset, src)
}
#[inline(always)]
fn mov_freg64_stack32(_buf: &mut Vec<'_, u8>, _dst: X86_64FloatReg, _offset: i32) {
unimplemented!("loading floating point reg from stack not yet implemented for X86_64");
fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset: i32) {
movsd_freg64_base64_offset32(buf, dst, X86_64GeneralReg::RSP, offset)
}
#[inline(always)]
fn mov_reg64_stack32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32) {
mov_reg64_stack32(buf, dst, offset);
mov_reg64_base64_offset32(buf, dst, X86_64GeneralReg::RSP, offset)
}
#[inline(always)]
fn mov_stack32_freg64(_buf: &mut Vec<'_, u8>, _offset: i32, _src: X86_64FloatReg) {
unimplemented!("saving floating point reg to stack not yet implemented for X86_64");
fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64FloatReg) {
movsd_base64_offset32_freg64(buf, X86_64GeneralReg::RSP, offset, src)
}
#[inline(always)]
fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64GeneralReg) {
mov_stack32_reg64(buf, offset, src);
mov_base64_offset32_reg64(buf, X86_64GeneralReg::RSP, offset, src)
}
#[inline(always)]
@ -1102,55 +1101,47 @@ fn mov_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64Gene
}
}
/// `MOV r64,r/m64` -> Move r/m64 to r64. where m64 references the base pionter.
#[inline(always)]
fn mov_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32) {
// This can be optimized based on how many bytes the offset actually is.
// This function can probably be made to take any memory offset, I didn't feel like figuring it out rn.
// Also, this may technically be faster genration since stack operations should be so common.
let rex = add_reg_extension(dst, REX_W);
let dst_mod = (dst as u8 % 8) << 3;
buf.reserve(8);
buf.extend(&[rex, 0x8B, 0x85 + dst_mod]);
buf.extend(&offset.to_le_bytes());
}
// The following base and stack based operations could be optimized based on how many bytes the offset actually is.
/// `MOV r/m64,r64` -> Move r64 to r/m64. where m64 references the base pionter.
/// `MOV r/m64,r64` -> Move r64 to r/m64, where m64 references a base + offset.
#[inline(always)]
fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64GeneralReg) {
// This can be optimized based on how many bytes the offset actually is.
// This function can probably be made to take any memory offset, I didn't feel like figuring it out rn.
// Also, this may technically be faster genration since stack operations should be so common.
let rex = add_reg_extension(src, REX_W);
fn mov_base64_offset32_reg64(
buf: &mut Vec<'_, u8>,
base: X86_64GeneralReg,
offset: i32,
src: X86_64GeneralReg,
) {
let rex = add_rm_extension(base, REX_W);
let rex = add_reg_extension(src, rex);
let src_mod = (src as u8 % 8) << 3;
let base_mod = base as u8 % 8;
buf.reserve(8);
buf.extend(&[rex, 0x89, 0x85 + src_mod]);
buf.extend(&[rex, 0x89, 0x80 + src_mod + base_mod]);
// Using RSP or R12 requires a secondary index byte.
if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 {
buf.push(0x24);
}
buf.extend(&offset.to_le_bytes());
}
/// `MOV r64,r/m64` -> Move r/m64 to r64. where m64 references the stack pionter.
/// `MOV r64,r/m64` -> Move r/m64 to r64, where m64 references a base + offset.
#[inline(always)]
fn mov_reg64_stack32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32) {
// This can be optimized based on how many bytes the offset actually is.
// This function can probably be made to take any memory offset, I didn't feel like figuring it out rn.
// Also, this may technically be faster genration since stack operations should be so common.
let rex = add_reg_extension(dst, REX_W);
fn mov_reg64_base64_offset32(
buf: &mut Vec<'_, u8>,
dst: X86_64GeneralReg,
base: X86_64GeneralReg,
offset: i32,
) {
let rex = add_rm_extension(base, REX_W);
let rex = add_reg_extension(dst, rex);
let dst_mod = (dst as u8 % 8) << 3;
let base_mod = base as u8 % 8;
buf.reserve(8);
buf.extend(&[rex, 0x8B, 0x84 + dst_mod, 0x24]);
buf.extend(&offset.to_le_bytes());
}
/// `MOV r/m64,r64` -> Move r64 to r/m64. where m64 references the stack pionter.
#[inline(always)]
fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64GeneralReg) {
// This can be optimized based on how many bytes the offset actually is.
// This function can probably be made to take any memory offset, I didn't feel like figuring it out rn.
// Also, this may technically be faster genration since stack operations should be so common.
let rex = add_reg_extension(src, REX_W);
let src_mod = (src as u8 % 8) << 3;
buf.reserve(8);
buf.extend(&[rex, 0x89, 0x84 + src_mod, 0x24]);
buf.extend(&[rex, 0x8B, 0x80 + dst_mod + base_mod]);
// Using RSP or R12 requires a secondary index byte.
if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 {
buf.push(0x24);
}
buf.extend(&offset.to_le_bytes());
}
@ -1188,6 +1179,52 @@ fn movsd_freg64_rip_offset32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset:
buf.extend(&offset.to_le_bytes());
}
/// `MOVSD r/m64,xmm1` -> Move xmm1 to r/m64. where m64 references the base pionter.
#[inline(always)]
fn movsd_base64_offset32_freg64(
buf: &mut Vec<'_, u8>,
base: X86_64GeneralReg,
offset: i32,
src: X86_64FloatReg,
) {
let src_mod = (src as u8 % 8) << 3;
let base_mod = base as u8 % 8;
buf.reserve(10);
buf.push(0xF2);
if src as u8 > 7 || base as u8 > 7 {
buf.push(0x44);
}
buf.extend(&[0x0F, 0x11, 0x80 + src_mod + base_mod]);
// Using RSP or R12 requires a secondary index byte.
if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 {
buf.push(0x24);
}
buf.extend(&offset.to_le_bytes());
}
/// `MOVSD xmm1,r/m64` -> Move r/m64 to xmm1. where m64 references the base pionter.
#[inline(always)]
fn movsd_freg64_base64_offset32(
buf: &mut Vec<'_, u8>,
dst: X86_64FloatReg,
base: X86_64GeneralReg,
offset: i32,
) {
let dst_mod = (dst as u8 % 8) << 3;
let base_mod = base as u8 % 8;
buf.reserve(10);
buf.push(0xF2);
if dst as u8 > 7 || base as u8 > 7 {
buf.push(0x44);
}
buf.extend(&[0x0F, 0x10, 0x80 + dst_mod + base_mod]);
// Using RSP or R12 requires a secondary index byte.
if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 {
buf.push(0x24);
}
buf.extend(&offset.to_le_bytes());
}
/// `NEG r/m64` -> Two's complement negate r/m64.
#[inline(always)]
fn neg_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
@ -1509,6 +1546,90 @@ mod tests {
}
}
#[test]
fn test_movsd_freg64_base32() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for ((dst, offset), expected) in &[
(
(X86_64FloatReg::XMM0, TEST_I32),
vec![0xF2, 0x0F, 0x10, 0x85],
),
(
(X86_64FloatReg::XMM15, TEST_I32),
vec![0xF2, 0x44, 0x0F, 0x10, 0xBD],
),
] {
buf.clear();
movsd_freg64_base64_offset32(&mut buf, *dst, X86_64GeneralReg::RBP, *offset);
assert_eq!(expected, &buf[..buf.len() - 4]);
assert_eq!(TEST_I32.to_le_bytes(), &buf[buf.len() - 4..]);
}
}
#[test]
fn test_movsd_base32_freg64() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for ((offset, src), expected) in &[
(
(TEST_I32, X86_64FloatReg::XMM0),
vec![0xF2, 0x0F, 0x11, 0x85],
),
(
(TEST_I32, X86_64FloatReg::XMM15),
vec![0xF2, 0x44, 0x0F, 0x11, 0xBD],
),
] {
buf.clear();
movsd_base64_offset32_freg64(&mut buf, X86_64GeneralReg::RBP, *offset, *src);
assert_eq!(expected, &buf[..buf.len() - 4]);
assert_eq!(TEST_I32.to_le_bytes(), &buf[buf.len() - 4..]);
}
}
#[test]
fn test_movsd_freg64_stack32() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for ((dst, offset), expected) in &[
(
(X86_64FloatReg::XMM0, TEST_I32),
vec![0xF2, 0x0F, 0x10, 0x84, 0x24],
),
(
(X86_64FloatReg::XMM15, TEST_I32),
vec![0xF2, 0x44, 0x0F, 0x10, 0xBC, 0x24],
),
] {
buf.clear();
movsd_freg64_base64_offset32(&mut buf, *dst, X86_64GeneralReg::RSP, *offset);
assert_eq!(expected, &buf[..buf.len() - 4]);
assert_eq!(TEST_I32.to_le_bytes(), &buf[buf.len() - 4..]);
}
}
#[test]
fn test_movsd_stack32_freg64() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for ((offset, src), expected) in &[
(
(TEST_I32, X86_64FloatReg::XMM0),
vec![0xF2, 0x0F, 0x11, 0x84, 0x24],
),
(
(TEST_I32, X86_64FloatReg::XMM15),
vec![0xF2, 0x44, 0x0F, 0x11, 0xBC, 0x24],
),
] {
buf.clear();
movsd_base64_offset32_freg64(&mut buf, X86_64GeneralReg::RSP, *offset, *src);
assert_eq!(expected, &buf[..buf.len() - 4]);
assert_eq!(TEST_I32.to_le_bytes(), &buf[buf.len() - 4..]);
}
}
#[test]
fn test_mov_reg64_base32() {
let arena = bumpalo::Bump::new();
@ -1518,7 +1639,7 @@ mod tests {
((X86_64GeneralReg::R15, TEST_I32), [0x4C, 0x8B, 0xBD]),
] {
buf.clear();
mov_reg64_base32(&mut buf, *dst, *offset);
mov_reg64_base64_offset32(&mut buf, *dst, X86_64GeneralReg::RBP, *offset);
assert_eq!(expected, &buf[..3]);
assert_eq!(TEST_I32.to_le_bytes(), &buf[3..]);
}
@ -1533,7 +1654,7 @@ mod tests {
((TEST_I32, X86_64GeneralReg::R15), [0x4C, 0x89, 0xBD]),
] {
buf.clear();
mov_base32_reg64(&mut buf, *offset, *src);
mov_base64_offset32_reg64(&mut buf, X86_64GeneralReg::RBP, *offset, *src);
assert_eq!(expected, &buf[..3]);
assert_eq!(TEST_I32.to_le_bytes(), &buf[3..]);
}
@ -1548,7 +1669,7 @@ mod tests {
((X86_64GeneralReg::R15, TEST_I32), [0x4C, 0x8B, 0xBC, 0x24]),
] {
buf.clear();
mov_reg64_stack32(&mut buf, *dst, *offset);
mov_reg64_base64_offset32(&mut buf, *dst, X86_64GeneralReg::RSP, *offset);
assert_eq!(expected, &buf[..4]);
assert_eq!(TEST_I32.to_le_bytes(), &buf[4..]);
}
@ -1563,7 +1684,7 @@ mod tests {
((TEST_I32, X86_64GeneralReg::R15), [0x4C, 0x89, 0xBC, 0x24]),
] {
buf.clear();
mov_stack32_reg64(&mut buf, *offset, *src);
mov_base64_offset32_reg64(&mut buf, X86_64GeneralReg::RSP, *offset, *src);
assert_eq!(expected, &buf[..4]);
assert_eq!(TEST_I32.to_le_bytes(), &buf[4..]);
}

View File

@ -3,6 +3,7 @@
#![allow(clippy::large_enum_variant)]
use bumpalo::{collections::Vec, Bump};
use roc_builtins::bitcode;
use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{ModuleName, TagName};
use roc_module::low_level::LowLevel;
@ -166,21 +167,33 @@ where
ret_layout,
..
} => {
// For most builtins instead of calling a function, we can just inline the low level.
match *func_sym {
Symbol::NUM_ABS => {
// Instead of calling the function, just inline it.
self.build_run_low_level(sym, &LowLevel::NumAbs, arguments, layout)
}
Symbol::NUM_ADD => {
// Instead of calling the function, just inline it.
self.build_run_low_level(sym, &LowLevel::NumAdd, arguments, layout)
}
Symbol::NUM_ACOS => {
self.build_run_low_level(sym, &LowLevel::NumAcos, arguments, layout)
}
Symbol::NUM_ASIN => {
self.build_run_low_level(sym, &LowLevel::NumAsin, arguments, layout)
}
Symbol::NUM_ATAN => {
self.build_run_low_level(sym, &LowLevel::NumAtan, arguments, layout)
}
Symbol::NUM_POW_INT => self.build_run_low_level(
sym,
&LowLevel::NumPowInt,
arguments,
layout,
),
Symbol::NUM_SUB => {
// Instead of calling the function, just inline it.
self.build_run_low_level(sym, &LowLevel::NumSub, arguments, layout)
}
Symbol::BOOL_EQ => {
// Instead of calling the function, just inline it.
self.build_run_low_level(sym, &LowLevel::Eq, arguments, layout)
}
x if x
@ -239,6 +252,34 @@ where
x => Err(format!("layout, {:?}, not implemented yet", x)),
}
}
LowLevel::NumAcos => self.build_fn_call(
sym,
bitcode::NUM_ACOS.to_string(),
args,
&[layout.clone()],
layout,
),
LowLevel::NumAsin => self.build_fn_call(
sym,
bitcode::NUM_ASIN.to_string(),
args,
&[layout.clone()],
layout,
),
LowLevel::NumAtan => self.build_fn_call(
sym,
bitcode::NUM_ATAN.to_string(),
args,
&[layout.clone()],
layout,
),
LowLevel::NumPowInt => self.build_fn_call(
sym,
bitcode::NUM_POW_INT.to_string(),
args,
&[layout.clone(), layout.clone()],
layout,
),
LowLevel::NumSub => {
// TODO: when this is expanded to floats. deal with typecasting here, and then call correct low level method.
match layout {

View File

@ -2,7 +2,7 @@ use crate::generic64::{aarch64, x86_64, Backend64Bit};
use crate::{Backend, Env, Relocation};
use bumpalo::collections::Vec;
use object::write;
use object::write::{Object, StandardSection, Symbol, SymbolSection};
use object::write::{Object, StandardSection, StandardSegment, Symbol, SymbolSection};
use object::{
Architecture, BinaryFormat, Endianness, RelocationEncoding, RelocationKind, SectionKind,
SymbolFlags, SymbolKind, SymbolScope,
@ -71,7 +71,6 @@ fn build_object<'a, B: Backend<'a>>(
mut backend: B,
mut output: Object,
) -> Result<Object, String> {
let text = output.section_id(StandardSection::Text);
let data_section = output.section_id(StandardSection::Data);
let comment = output.add_section(vec![], b"comment".to_vec(), SectionKind::OtherString);
output.append_section_data(
@ -88,6 +87,12 @@ fn build_object<'a, B: Backend<'a>>(
.get(sym, &layout)
.to_symbol_string(sym, &env.interns);
let section_id = output.add_section(
output.segment_name(StandardSegment::Text).to_vec(),
format!(".text.{}", fn_name).as_bytes().to_vec(),
SectionKind::Text,
);
let proc_symbol = Symbol {
name: fn_name.as_bytes().to_vec(),
value: 0,
@ -101,19 +106,19 @@ fn build_object<'a, B: Backend<'a>>(
SymbolScope::Linkage
},
weak: false,
section: SymbolSection::Section(text),
section: SymbolSection::Section(section_id),
flags: SymbolFlags::None,
};
let proc_id = output.add_symbol(proc_symbol);
procs.push((fn_name, proc_id, proc));
procs.push((fn_name, section_id, proc_id, proc));
}
// Build procedures.
let mut relocations = bumpalo::vec![in env.arena];
for (fn_name, proc_id, proc) in procs {
for (fn_name, section_id, proc_id, proc) in procs {
let mut local_data_index = 0;
let (proc_data, relocs) = backend.build_proc(proc)?;
let proc_offset = output.add_symbol_data(proc_id, text, proc_data, 16);
let proc_offset = output.add_symbol_data(proc_id, section_id, proc_data, 16);
for reloc in relocs {
let elfreloc = match reloc {
Relocation::LocalData { offset, data } => {
@ -126,7 +131,7 @@ fn build_object<'a, B: Backend<'a>>(
kind: SymbolKind::Data,
scope: SymbolScope::Compilation,
weak: false,
section: write::SymbolSection::Section(data_section),
section: SymbolSection::Section(data_section),
flags: SymbolFlags::None,
};
local_data_index += 1;
@ -152,10 +157,26 @@ fn build_object<'a, B: Backend<'a>>(
addend: -4,
}
} else {
return Err(format!("failed to find symbol for {:?}", name));
return Err(format!("failed to find data symbol for {:?}", name));
}
}
Relocation::LinkedFunction { offset, name } => {
// If the symbol is an undefined zig builtin, we need to add it here.
if output.symbol_id(name.as_bytes()) == None
&& name.starts_with("roc_builtins.")
{
let builtin_symbol = Symbol {
name: name.as_bytes().to_vec(),
value: 0,
size: 0,
kind: SymbolKind::Text,
scope: SymbolScope::Linkage,
weak: false,
section: SymbolSection::Undefined,
flags: SymbolFlags::None,
};
output.add_symbol(builtin_symbol);
}
if let Some(sym_id) = output.symbol_id(name.as_bytes()) {
write::Relocation {
offset: offset + proc_offset,
@ -166,16 +187,16 @@ fn build_object<'a, B: Backend<'a>>(
addend: -4,
}
} else {
return Err(format!("failed to find symbol for {:?}", name));
return Err(format!("failed to find fn symbol for {:?}", name));
}
}
};
relocations.push(elfreloc);
relocations.push((section_id, elfreloc));
}
}
for reloc in relocations {
for (section_id, reloc) in relocations {
output
.add_relocation(text, reloc)
.add_relocation(section_id, reloc)
.map_err(|e| format!("{:?}", e))?;
}
Ok(output)

View File

@ -208,6 +208,26 @@ mod gen_num {
);
}
#[test]
fn pow_int() {
assert_evals_to!("Num.powInt 2 3", 8, i64);
}
#[test]
fn acos() {
assert_evals_to!("Num.acos 0.5", 1.0471975511965979, f64);
}
#[test]
fn asin() {
assert_evals_to!("Num.asin 0.5", 0.5235987755982989, f64);
}
#[test]
fn atan() {
assert_evals_to!("Num.atan 10", 1.4711276743037347, f64);
}
#[test]
fn gen_if_fn() {
assert_evals_to!(
@ -737,16 +757,6 @@ mod gen_num {
assert_evals_to!("Num.floor 1.9", 1, i64);
}
#[test]
fn pow_int() {
assert_evals_to!("Num.powInt 2 3", 8, i64);
}
#[test]
fn atan() {
assert_evals_to!("Num.atan 10", 1.4711276743037347, f64);
}
// #[test]
// #[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)]
// fn int_overflow() {
@ -845,6 +855,19 @@ mod gen_num {
// );
// }
#[test]
fn max_i128() {
assert_evals_to!(
indoc!(
r#"
Num.maxI128
"#
),
i128::MAX,
i128
);
}
#[test]
fn num_max_int() {
assert_evals_to!(

View File

@ -1,5 +1,6 @@
use libloading::Library;
use roc_build::link::{link, LinkType};
use roc_builtins::bitcode;
use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap;
use tempfile::tempdir;
@ -175,10 +176,14 @@ pub fn helper<'a>(
.expect("failed to build output object");
std::fs::write(&app_o_file, module_out).expect("failed to write object to file");
// std::fs::copy(&app_o_file, "/tmp/app.o").unwrap();
let (mut child, dylib_path) = link(
&target,
app_o_file.clone(),
&[app_o_file.to_str().unwrap()],
// Long term we probably want a smarter way to link in zig builtins.
// With the current method all methods are kept and it adds about 100k to all outputs.
&[app_o_file.to_str().unwrap(), bitcode::OBJ_PATH],
LinkType::Dylib,
)
.expect("failed to link dynamic library");
@ -188,7 +193,6 @@ pub fn helper<'a>(
// Load the dylib
let path = dylib_path.as_path().to_str().unwrap();
// std::fs::copy(&app_o_file, "/tmp/app.o").unwrap();
// std::fs::copy(&path, "/tmp/libapp.so").unwrap();
let lib = Library::new(path).expect("failed to load shared library");

View File

@ -19,6 +19,7 @@ roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
roc_reporting = { path = "../reporting" }
ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1"
parking_lot = { version = "0.11", features = ["deadlock_detection"] }

View File

@ -22,7 +22,7 @@ use roc_mono::ir::{
CapturedSymbols, ExternalSpecializations, PartialProc, PendingSpecialization, Proc, Procs,
};
use roc_mono::layout::{Layout, LayoutCache, LayoutProblem};
use roc_parse::ast::{self, Attempting, StrLiteral, TypeAnnotation};
use roc_parse::ast::{self, StrLiteral, TypeAnnotation};
use roc_parse::header::{
ExposesEntry, ImportsEntry, PackageEntry, PackageOrPath, PlatformHeader, To, TypedIdent,
};
@ -358,12 +358,15 @@ struct ModuleCache<'a> {
external_specializations_requested: MutMap<ModuleId, ExternalSpecializations>,
/// Various information
imports: MutMap<ModuleId, MutSet<ModuleId>>,
top_level_thunks: MutMap<ModuleId, MutSet<Symbol>>,
documentation: MutMap<ModuleId, ModuleDocumentation>,
can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
type_problems: MutMap<ModuleId, Vec<solve::TypeError>>,
mono_problems: MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>,
sources: MutMap<ModuleId, (PathBuf, &'a str)>,
header_sources: MutMap<ModuleId, (PathBuf, &'a str)>,
}
fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) -> Vec<BuildTask<'a>> {
@ -544,11 +547,24 @@ fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) ->
ident_ids,
} = typechecked;
let mut imported_module_thunks = MutSet::default();
if let Some(imports) = state.module_cache.imports.get(&module_id) {
for imported in imports.iter() {
imported_module_thunks.extend(
state.module_cache.top_level_thunks[imported]
.iter()
.copied(),
);
}
}
BuildTask::BuildPendingSpecializations {
layout_cache,
module_id,
module_timing,
solved_subs,
imported_module_thunks,
decls,
ident_ids,
exposed_to_host: state.exposed_to_host.clone(),
@ -601,6 +617,7 @@ pub struct LoadedModule {
pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>,
pub declarations_by_id: MutMap<ModuleId, Vec<Declaration>>,
pub exposed_to_host: MutMap<Symbol, Variable>,
pub header_sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub timings: MutMap<ModuleId, ModuleTiming>,
pub documentation: MutMap<ModuleId, ModuleDocumentation>,
@ -616,6 +633,7 @@ struct ModuleHeader<'a> {
module_id: ModuleId,
module_name: ModuleNameEnum<'a>,
module_path: PathBuf,
is_root_module: bool,
exposed_ident_ids: IdentIds,
deps_by_name: MutMap<PQModuleName<'a>, ModuleId>,
packages: MutMap<&'a str, PackageOrPath<'a>>,
@ -623,7 +641,8 @@ struct ModuleHeader<'a> {
package_qualified_imported_modules: MutSet<PackageQualified<'a, ModuleId>>,
exposes: Vec<Symbol>,
exposed_imports: MutMap<Ident, (Symbol, Region)>,
src: &'a [u8],
header_src: &'a str,
parse_state: roc_parse::parser::State<'a>,
module_timing: ModuleTiming,
}
@ -682,6 +701,7 @@ pub struct MonomorphizedModule<'a> {
pub mono_problems: MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>,
pub procedures: MutMap<(Symbol, Layout<'a>), Proc<'a>>,
pub exposed_to_host: MutMap<Symbol, Variable>,
pub header_sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub timings: MutMap<ModuleId, ModuleTiming>,
}
@ -766,6 +786,14 @@ enum Msg<'a> {
FailedToParse(ParseProblem<'a, SyntaxError<'a>>),
}
#[derive(Debug)]
enum PlatformPath<'a> {
NotSpecified,
Valid(To<'a>),
RootIsInterface,
RootIsPkgConfig,
}
#[derive(Debug)]
struct State<'a> {
pub root_id: ModuleId,
@ -774,7 +802,7 @@ struct State<'a> {
pub stdlib: &'a StdLib,
pub exposed_types: SubsByModule,
pub output_path: Option<&'a str>,
pub platform_path: Option<To<'a>>,
pub platform_path: PlatformPath<'a>,
pub headers_parsed: MutSet<ModuleId>,
@ -890,26 +918,19 @@ impl ModuleTiming {
end_time,
} = self;
end_time
.duration_since(*start_time)
.ok()
.and_then(|t| {
t.checked_sub(*make_specializations).and_then(|t| {
t.checked_sub(*find_specializations).and_then(|t| {
t.checked_sub(*solve).and_then(|t| {
t.checked_sub(*constrain).and_then(|t| {
t.checked_sub(*canonicalize).and_then(|t| {
t.checked_sub(*parse_body).and_then(|t| {
t.checked_sub(*parse_header)
.and_then(|t| t.checked_sub(*read_roc_file))
})
})
})
})
})
})
})
.unwrap_or_else(Duration::default)
let calculate = |t: Result<Duration, std::time::SystemTimeError>| -> Option<Duration> {
t.ok()?
.checked_sub(*make_specializations)?
.checked_sub(*find_specializations)?
.checked_sub(*solve)?
.checked_sub(*constrain)?
.checked_sub(*canonicalize)?
.checked_sub(*parse_body)?
.checked_sub(*parse_header)?
.checked_sub(*read_roc_file)
};
calculate(end_time.duration_since(*start_time)).unwrap_or_else(Duration::default)
}
}
@ -948,6 +969,7 @@ enum BuildTask<'a> {
module_timing: ModuleTiming,
layout_cache: LayoutCache<'a>,
solved_subs: Solved<Subs>,
imported_module_thunks: MutSet<Symbol>,
module_id: ModuleId,
ident_ids: IdentIds,
decls: Vec<Declaration>,
@ -978,6 +1000,8 @@ pub enum LoadingProblem<'a> {
},
ParsingFailed(ParseProblem<'a, SyntaxError<'a>>),
UnexpectedHeader(String),
/// there is no platform (likely running an Interface module)
NoPlatform(String),
MsgChannelDied,
ErrJoiningWorkerThreads,
@ -1131,6 +1155,7 @@ impl<'a> LoadStart<'a> {
load_filename(
arena,
filename,
true,
None,
Arc::clone(&arc_modules),
Arc::clone(&ident_ids_by_module),
@ -1399,7 +1424,7 @@ where
goal_phase,
stdlib,
output_path: None,
platform_path: None,
platform_path: PlatformPath::NotSpecified,
module_cache: ModuleCache::default(),
dependencies: Dependencies::default(),
procedures: MutMap::default(),
@ -1476,7 +1501,7 @@ where
state,
subs,
exposed_to_host,
)));
)?));
}
Msg::FailedToParse(problem) => {
// Shut down all the worker threads.
@ -1607,16 +1632,25 @@ fn update<'a>(
match header_extra {
App { to_platform } => {
debug_assert_eq!(state.platform_path, None);
state.platform_path = Some(to_platform.clone());
debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified));
state.platform_path = PlatformPath::Valid(to_platform.clone());
}
PkgConfig { .. } => {
debug_assert_eq!(state.platform_id, None);
state.platform_id = Some(header.module_id);
if header.is_root_module {
debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified));
state.platform_path = PlatformPath::RootIsPkgConfig;
}
}
Interface => {
if header.is_root_module {
debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified));
state.platform_path = PlatformPath::RootIsInterface;
}
}
Interface => {}
}
// store an ID to name mapping, so we know the file to read when fetching dependencies' headers
@ -1641,6 +1675,23 @@ fn update<'a>(
.exposed_symbols_by_module
.insert(home, exposed_symbols);
state
.module_cache
.header_sources
.insert(home, (header.module_path.clone(), header.header_src));
state
.module_cache
.imports
.entry(header.module_id)
.or_default()
.extend(
header
.package_qualified_imported_modules
.iter()
.map(|x| *x.as_inner()),
);
work.extend(state.dependencies.add_module(
header.module_id,
&header.package_qualified_imported_modules,
@ -1904,6 +1955,13 @@ fn update<'a>(
}
}
state
.module_cache
.top_level_thunks
.entry(module_id)
.or_default()
.extend(procs.module_thunks.iter().copied());
let found_specializations_module = FoundSpecializationsModule {
layout_cache,
module_id,
@ -2035,7 +2093,7 @@ fn finish_specialization(
state: State,
subs: Subs,
exposed_to_host: MutMap<Symbol, Variable>,
) -> MonomorphizedModule {
) -> Result<MonomorphizedModule, LoadingProblem> {
let module_ids = Arc::try_unwrap(state.arc_modules)
.unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids"))
.into_inner()
@ -2059,24 +2117,98 @@ fn finish_specialization(
type_problems,
can_problems,
sources,
header_sources,
..
} = module_cache;
let sources = sources
let sources: MutMap<ModuleId, (PathBuf, Box<str>)> = sources
.into_iter()
.map(|(id, (path, src))| (id, (path, src.into())))
.collect();
let header_sources: MutMap<ModuleId, (PathBuf, Box<str>)> = header_sources
.into_iter()
.map(|(id, (path, src))| (id, (path, src.into())))
.collect();
let path_to_platform = {
use PlatformPath::*;
let package_or_path = match platform_path {
Some(To::ExistingPackage(shorthand)) => {
Valid(To::ExistingPackage(shorthand)) => {
match (*state.arc_shorthands).lock().get(shorthand) {
Some(p_or_p) => p_or_p.clone(),
None => unreachable!(),
}
}
Some(To::NewPackage(p_or_p)) => p_or_p,
None => panic!("no platform!"),
Valid(To::NewPackage(p_or_p)) => p_or_p,
other => {
use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE};
use ven_pretty::DocAllocator;
let module_id = state.root_id;
let palette = DEFAULT_PALETTE;
// Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&[], module_id, &interns);
let report = {
match other {
Valid(_) => unreachable!(),
NotSpecified => {
let doc = alloc.stack(vec![
alloc.reflow("I could not find a platform based on your input file."),
alloc.reflow(r"Does the module header contain an entry that looks like this:"),
alloc
.parser_suggestion(" packages { base: \"platform\" }")
.indent(4),
alloc.reflow("See also TODO."),
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "NO PLATFORM".to_string(),
}
}
RootIsInterface => {
let doc = alloc.stack(vec![
alloc.reflow(r"The input file is a interface file, but only app modules can be ran."),
alloc.concat(vec![
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"),
alloc.reflow(r"but won't output any executable."),
])
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "NO PLATFORM".to_string(),
}
}
RootIsPkgConfig => {
let doc = alloc.stack(vec![
alloc.reflow(r"The input file is a package config file, but only app modules can be ran."),
alloc.concat(vec![
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"),
alloc.reflow(r"but won't output any executable."),
])
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "NO PLATFORM".to_string(),
}
}
}
};
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
return Err(LoadingProblem::NoPlatform(buf));
}
};
match package_or_path {
@ -2088,7 +2220,7 @@ fn finish_specialization(
let platform_path = path_to_platform.into();
MonomorphizedModule {
Ok(MonomorphizedModule {
can_problems,
mono_problems,
type_problems,
@ -2100,8 +2232,9 @@ fn finish_specialization(
interns,
procedures,
sources,
header_sources,
timings: state.timings,
}
})
}
fn finish(
@ -2127,6 +2260,13 @@ fn finish(
.map(|(id, (path, src))| (id, (path, src.into())))
.collect();
let header_sources = state
.module_cache
.header_sources
.into_iter()
.map(|(id, (path, src))| (id, (path, src.into())))
.collect();
LoadedModule {
module_id: state.root_id,
interns,
@ -2135,6 +2275,7 @@ fn finish(
type_problems: state.module_cache.type_problems,
declarations_by_id: state.declarations_by_id,
exposed_to_host: exposed_vars_by_symbol.into_iter().collect(),
header_sources,
sources,
timings: state.timings,
documentation,
@ -2163,8 +2304,8 @@ fn load_pkg_config<'a>(
Ok(bytes_vec) => {
let parse_start = SystemTime::now();
let bytes = arena.alloc(bytes_vec);
let parse_state = parser::State::new_in(arena, bytes, Attempting::Module);
let parsed = roc_parse::module::header().parse(&arena, parse_state);
let parse_state = parser::State::new(bytes);
let parsed = roc_parse::module::parse_header(&arena, parse_state);
let parse_header_duration = parse_start.elapsed().unwrap();
// Insert the first entries for this module's timings
@ -2178,19 +2319,19 @@ fn load_pkg_config<'a>(
effect_module_timing.parse_header = parse_header_duration;
match parsed {
Ok((_, ast::Module::Interface { header }, _parse_state)) => {
Ok((ast::Module::Interface { header }, _parse_state)) => {
Err(LoadingProblem::UnexpectedHeader(format!(
"expected platform/package module, got Interface with header\n{:?}",
header
)))
}
Ok((_, ast::Module::App { header }, _parse_state)) => {
Ok((ast::Module::App { header }, _parse_state)) => {
Err(LoadingProblem::UnexpectedHeader(format!(
"expected platform/package module, got App with header\n{:?}",
header
)))
}
Ok((_, ast::Module::Platform { header }, parser_state)) => {
Ok((ast::Module::Platform { header }, parser_state)) => {
// make a Pkg-Config module that ultimately exposes `main` to the host
let pkg_config_module_msg = fabricate_pkg_config_module(
arena,
@ -2203,7 +2344,7 @@ fn load_pkg_config<'a>(
&header,
pkg_module_timing,
)
.map(|x| x.1)?;
.1;
let effects_module_msg = fabricate_effects_module(
arena,
@ -2214,12 +2355,12 @@ fn load_pkg_config<'a>(
header,
effect_module_timing,
)
.map(|x| x.1)?;
.1;
Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg]))
}
Err((_, fail, _)) => Err(LoadingProblem::ParsingFailed(
fail.into_parse_problem(filename, bytes),
Err(fail) => Err(LoadingProblem::ParsingFailed(
SyntaxError::Header(fail).into_parse_problem(filename, bytes),
)),
}
}
@ -2284,6 +2425,7 @@ fn load_module<'a>(
load_filename(
arena,
filename,
false,
opt_shorthand,
module_ids,
ident_ids_by_module,
@ -2323,6 +2465,7 @@ fn parse_header<'a>(
arena: &'a Bump,
read_file_duration: Duration,
filename: PathBuf,
is_root_module: bool,
opt_shorthand: Option<&'a str>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
@ -2331,8 +2474,8 @@ fn parse_header<'a>(
start_time: SystemTime,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let parse_start = SystemTime::now();
let parse_state = parser::State::new_in(arena, src_bytes, Attempting::Module);
let parsed = roc_parse::module::header().parse(&arena, parse_state);
let parse_state = parser::State::new(src_bytes);
let parsed = roc_parse::module::parse_header(&arena, parse_state);
let parse_header_duration = parse_start.elapsed().unwrap();
// Insert the first entries for this module's timings
@ -2342,39 +2485,63 @@ fn parse_header<'a>(
module_timing.parse_header = parse_header_duration;
match parsed {
Ok((_, ast::Module::Interface { header }, parse_state)) => Ok(send_header(
Located {
region: header.name.region,
value: ModuleNameEnum::Interface(header.name.value),
},
filename,
opt_shorthand,
&[],
header.exposes.into_bump_slice(),
header.imports.into_bump_slice(),
None,
parse_state,
module_ids,
ident_ids_by_module,
module_timing,
)),
Ok((_, ast::Module::App { header }, parse_state)) => {
Ok((ast::Module::Interface { header }, parse_state)) => {
let header_src = unsafe {
let chomped = src_bytes.len() - parse_state.bytes.len();
std::str::from_utf8_unchecked(&src_bytes[..chomped])
};
let info = HeaderInfo {
loc_name: Located {
region: header.name.region,
value: ModuleNameEnum::Interface(header.name.value),
},
filename,
is_root_module,
opt_shorthand,
header_src,
packages: &[],
exposes: header.exposes.into_bump_slice(),
imports: header.imports.into_bump_slice(),
to_platform: None,
};
Ok(send_header(
info,
parse_state,
module_ids,
ident_ids_by_module,
module_timing,
))
}
Ok((ast::Module::App { header }, parse_state)) => {
let mut pkg_config_dir = filename.clone();
pkg_config_dir.pop();
let header_src = unsafe {
let chomped = src_bytes.len() - parse_state.bytes.len();
std::str::from_utf8_unchecked(&src_bytes[..chomped])
};
let packages = header.packages.into_bump_slice();
let (module_id, app_module_header_msg) = send_header(
Located {
let info = HeaderInfo {
loc_name: Located {
region: header.name.region,
value: ModuleNameEnum::App(header.name.value),
},
filename,
is_root_module,
opt_shorthand,
header_src,
packages,
header.provides.into_bump_slice(),
header.imports.into_bump_slice(),
Some(header.to.value.clone()),
exposes: header.provides.into_bump_slice(),
imports: header.imports.into_bump_slice(),
to_platform: Some(header.to.value.clone()),
};
let (module_id, app_module_header_msg) = send_header(
info,
parse_state,
module_ids.clone(),
ident_ids_by_module.clone(),
@ -2456,7 +2623,7 @@ fn parse_header<'a>(
},
}
}
Ok((_, ast::Module::Platform { header }, _parse_state)) => fabricate_effects_module(
Ok((ast::Module::Platform { header }, _parse_state)) => Ok(fabricate_effects_module(
arena,
&"",
module_ids,
@ -2464,17 +2631,19 @@ fn parse_header<'a>(
mode,
header,
module_timing,
),
Err((_, fail, _)) => Err(LoadingProblem::ParsingFailed(
fail.into_parse_problem(filename, src_bytes),
)),
Err(fail) => Err(LoadingProblem::ParsingFailed(
SyntaxError::Header(fail).into_parse_problem(filename, src_bytes),
)),
}
}
/// Load a module by its filename
#[allow(clippy::too_many_arguments)]
fn load_filename<'a>(
arena: &'a Bump,
filename: PathBuf,
is_root_module: bool,
opt_shorthand: Option<&'a str>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
@ -2490,6 +2659,7 @@ fn load_filename<'a>(
arena,
file_io_duration,
filename,
is_root_module,
opt_shorthand,
module_ids,
ident_ids_by_module,
@ -2524,6 +2694,7 @@ fn load_from_str<'a>(
arena,
file_io_duration,
filename,
false,
None,
module_ids,
ident_ids_by_module,
@ -2541,15 +2712,22 @@ enum ModuleNameEnum<'a> {
PkgConfig,
}
#[allow(clippy::too_many_arguments)]
fn send_header<'a>(
#[derive(Debug)]
struct HeaderInfo<'a> {
loc_name: Located<ModuleNameEnum<'a>>,
filename: PathBuf,
is_root_module: bool,
opt_shorthand: Option<&'a str>,
header_src: &'a str,
packages: &'a [Located<PackageEntry<'a>>],
exposes: &'a [Located<ExposesEntry<'a, &'a str>>],
imports: &'a [Located<ImportsEntry<'a>>],
to_platform: Option<To<'a>>,
}
#[allow(clippy::too_many_arguments)]
fn send_header<'a>(
info: HeaderInfo<'a>,
parse_state: parser::State<'a>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
@ -2557,6 +2735,18 @@ fn send_header<'a>(
) -> (ModuleId, Msg<'a>) {
use ModuleNameEnum::*;
let HeaderInfo {
loc_name,
filename,
is_root_module,
opt_shorthand,
packages,
exposes,
imports,
to_platform,
header_src,
} = info;
let declared_name: ModuleName = match &loc_name.value {
PkgConfig => unreachable!(),
App(_) => ModuleName::APP.into(),
@ -2731,6 +2921,7 @@ fn send_header<'a>(
ModuleHeader {
module_id: home,
module_path: filename,
is_root_module,
exposed_ident_ids: ident_ids,
module_name: loc_name.value,
packages: package_entries,
@ -2738,7 +2929,8 @@ fn send_header<'a>(
package_qualified_imported_modules,
deps_by_name,
exposes: exposed,
src: parse_state.bytes,
header_src,
parse_state,
exposed_imports: scope,
module_timing,
},
@ -2752,6 +2944,7 @@ fn send_header<'a>(
fn send_header_two<'a>(
arena: &'a Bump,
filename: PathBuf,
is_root_module: bool,
shorthand: &'a str,
app_module_id: ModuleId,
packages: &'a [Located<PackageEntry<'a>>],
@ -2948,6 +3141,7 @@ fn send_header_two<'a>(
ModuleHeader {
module_id: home,
module_path: filename,
is_root_module,
exposed_ident_ids: ident_ids,
module_name,
packages: package_entries,
@ -2955,7 +3149,8 @@ fn send_header_two<'a>(
package_qualified_imported_modules,
deps_by_name,
exposes: exposed,
src: parse_state.bytes,
header_src: "#builtin effect header",
parse_state,
exposed_imports: scope,
module_timing,
},
@ -3084,13 +3279,14 @@ fn fabricate_pkg_config_module<'a>(
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
header: &PlatformHeader<'a>,
module_timing: ModuleTiming,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
) -> (ModuleId, Msg<'a>) {
let provides: &'a [Located<ExposesEntry<'a, &'a str>>] =
header.provides.clone().into_bump_slice();
Ok(send_header_two(
send_header_two(
arena,
filename,
false,
shorthand,
app_module_id,
&[],
@ -3101,7 +3297,7 @@ fn fabricate_pkg_config_module<'a>(
module_ids,
ident_ids_by_module,
module_timing,
))
)
}
#[allow(clippy::too_many_arguments)]
@ -3113,7 +3309,7 @@ fn fabricate_effects_module<'a>(
mode: Mode,
header: PlatformHeader<'a>,
module_timing: ModuleTiming,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
) -> (ModuleId, Msg<'a>) {
let num_exposes = header.provides.len() + 1;
let mut exposed: Vec<Symbol> = Vec::with_capacity(num_exposes);
@ -3341,7 +3537,7 @@ fn fabricate_effects_module<'a>(
module_timing,
};
Ok((
(
module_id,
Msg::MadeEffectModule {
type_shortname: effects.effect_shortname,
@ -3349,7 +3545,7 @@ fn fabricate_effects_module<'a>(
canonicalization_problems: module_output.problems,
module_docs,
},
))
)
}
fn unpack_exposes_entries<'a>(
@ -3384,6 +3580,7 @@ fn unpack_exposes_entries<'a>(
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::unnecessary_wraps)]
fn canonicalize_and_constrain<'a, F>(
arena: &'a Bump,
module_ids: &ModuleIds,
@ -3481,12 +3678,13 @@ where
fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, LoadingProblem<'a>> {
let mut module_timing = header.module_timing;
let parse_start = SystemTime::now();
let parse_state = parser::State::new_in(arena, &header.src, Attempting::Module);
let source = header.parse_state.bytes;
let parse_state = header.parse_state;
let parsed_defs = match module_defs().parse(&arena, parse_state) {
Ok((_, success, _state)) => success,
Err((_, fail, _)) => {
return Err(LoadingProblem::ParsingFailed(
fail.into_parse_problem(header.module_path, header.src),
fail.into_parse_problem(header.module_path, source),
));
}
};
@ -3504,7 +3702,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, Loadi
// SAFETY: By this point we've already incrementally verified that there
// are no UTF-8 errors in these bytes. If there had been any UTF-8 errors,
// we'd have bailed out before now.
let src = unsafe { from_utf8_unchecked(header.src) };
let src = unsafe { from_utf8_unchecked(source) };
let ModuleHeader {
module_id,
@ -3644,6 +3842,7 @@ fn make_specializations<'a>(
fn build_pending_specializations<'a>(
arena: &'a Bump,
solved_subs: Solved<Subs>,
imported_module_thunks: MutSet<Symbol>,
home: ModuleId,
mut ident_ids: IdentIds,
decls: Vec<Declaration>,
@ -3656,6 +3855,9 @@ fn build_pending_specializations<'a>(
let find_specializations_start = SystemTime::now();
let mut procs = Procs::default();
debug_assert!(procs.imported_module_thunks.is_empty());
procs.imported_module_thunks = imported_module_thunks;
let mut mono_problems = std::vec::Vec::new();
let mut subs = solved_subs.into_inner();
let mut mono_env = roc_mono::ir::Env {
@ -3937,10 +4139,12 @@ where
module_timing,
layout_cache,
solved_subs,
imported_module_thunks,
exposed_to_host,
} => Ok(build_pending_specializations(
arena,
solved_subs,
imported_module_thunks,
module_id,
ident_ids,
decls,

View File

@ -16,8 +16,8 @@ Model position :
initialModel : position -> Model position
initialModel = \start ->
{ evaluated : Set.empty
, openSet : Set.singleton start
, costs : Map.singleton start 0.0
, openSet : Set.single start
, costs : Dict.single start 0.0
, cameFrom : Map.empty
}
@ -42,7 +42,7 @@ cheapestOpen = \costFunction, model ->
else
Ok smallestSoFar
Set.foldl model.openSet folder (Err KeyNotFound)
Set.walk model.openSet folder (Err KeyNotFound)
|> Result.map (\x -> x.position)
@ -101,11 +101,11 @@ astar = \costFn, moveFn, goal, model ->
neighbours = moveFn current
newNeighbours = Set.diff neighbours modelPopped.evaluated
newNeighbours = Set.difference neighbours modelPopped.evaluated
modelWithNeighbours = { modelPopped & openSet : Set.union modelPopped.openSet newNeighbours }
modelWithCosts = Set.foldl newNeighbours (\nb, md -> updateCost current nb md) modelWithNeighbours
modelWithCosts = Set.walk newNeighbours (\nb, md -> updateCost current nb md) modelWithNeighbours
astar costFn moveFn goal modelWithCosts

View File

@ -16,8 +16,8 @@ Model position :
initialModel : position -> Model position
initialModel = \start ->
{ evaluated : Set.empty
, openSet : Set.singleton start
, costs : Dict.singleton start 0.0
, openSet : Set.single start
, costs : Dict.single start 0.0
, cameFrom : Dict.empty
}
@ -42,7 +42,7 @@ cheapestOpen = \costFunction, model ->
else
Ok smallestSoFar
Set.foldl model.openSet folder (Err KeyNotFound)
Set.walk model.openSet folder (Err KeyNotFound)
|> Result.map (\x -> x.position)
@ -101,11 +101,11 @@ astar = \costFn, moveFn, goal, model ->
neighbours = moveFn current
newNeighbours = Set.diff neighbours modelPopped.evaluated
newNeighbours = Set.difference neighbours modelPopped.evaluated
modelWithNeighbours = { modelPopped & openSet : Set.union modelPopped.openSet newNeighbours }
modelWithCosts = Set.foldl newNeighbours (\nb, md -> updateCost current nb md) modelWithNeighbours
modelWithCosts = Set.walk newNeighbours (\nb, md -> updateCost current nb md) modelWithNeighbours
astar costFn moveFn goal modelWithCosts

View File

@ -11,6 +11,9 @@ pub enum LowLevel {
StrSplit,
StrCountGraphemes,
StrFromInt,
StrFromUtf8,
StrToBytes,
StrFromFloat,
ListLen,
ListGetUnsafe,
ListSet,
@ -24,10 +27,15 @@ pub enum LowLevel {
ListPrepend,
ListJoin,
ListMap,
ListMap2,
ListMap3,
ListMapWithIndex,
ListKeepIf,
ListWalk,
ListWalkBackwards,
ListSum,
ListKeepOks,
ListKeepErrs,
DictSize,
DictEmpty,
DictInsert,
@ -36,6 +44,11 @@ pub enum LowLevel {
DictGetUnsafe,
DictKeys,
DictValues,
DictUnion,
DictIntersection,
DictDifference,
DictWalk,
SetFromList,
NumAdd,
NumAddWrap,
NumAddChecked,
@ -52,6 +65,7 @@ pub enum LowLevel {
NumCompare,
NumDivUnchecked,
NumRemUnchecked,
NumIsMultipleOf,
NumAbs,
NumNeg,
NumSin,
@ -69,6 +83,11 @@ pub enum LowLevel {
NumAsin,
NumBitwiseAnd,
NumBitwiseXor,
NumBitwiseOr,
NumShiftLeftBy,
NumShiftRightBy,
NumShiftRightZfBy,
NumIntCast,
Eq,
NotEq,
And,

View File

@ -42,6 +42,9 @@ pub enum BinOp {
And,
Or,
Pizza, // lowest precedence
Assignment,
HasType,
Backpassing,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@ -83,6 +86,7 @@ impl BinOp {
Equals | NotEquals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => {
NonAssociative
}
Assignment | HasType | Backpassing => unreachable!(),
}
}
@ -95,6 +99,7 @@ impl BinOp {
And => 3,
Or => 2,
Pizza => 1,
Assignment | HasType | Backpassing => unreachable!(),
}
}
}
@ -131,6 +136,9 @@ impl std::fmt::Display for BinOp {
And => "&&",
Or => "||",
Pizza => "|>",
Assignment => "=",
HasType => ":",
Backpassing => "<-",
};
write!(f, "{}", as_str)

View File

@ -749,6 +749,13 @@ define_builtins! {
17 GENERIC_RC_REF: "#generic_rc_by_ref" // refcount of arbitrary layouts, passed as an opaque pointer
18 GENERIC_EQ: "#generic_eq" // internal function that checks generic equality
// a user-defined function that we need to capture in a closure
// see e.g. Set.walk
19 USER_FUNCTION: "#user_function"
// A caller (wrapper) that we pass to zig for it to be able to call Roc functions
20 ZIG_FUNCTION_CALLER: "#zig_function_caller"
}
1 NUM: "Num" => {
0 NUM_NUM: "Num" imported // the Num.Num type alias
@ -834,15 +841,23 @@ define_builtins! {
80 NUM_BINARY32: "Binary32" imported
81 NUM_BITWISE_AND: "bitwiseAnd"
82 NUM_BITWISE_XOR: "bitwiseXor"
83 NUM_SUB_WRAP: "subWrap"
84 NUM_SUB_CHECKED: "subChecked"
85 NUM_MUL_WRAP: "mulWrap"
86 NUM_MUL_CHECKED: "mulChecked"
87 NUM_INT: "Int" imported
88 NUM_FLOAT: "Float" imported
89 NUM_AT_NATURAL: "@Natural"
90 NUM_NATURAL: "Natural" imported
91 NUM_NAT: "Nat" imported
83 NUM_BITWISE_OR: "bitwiseOr"
84 NUM_SHIFT_LEFT: "shiftLeftBy"
85 NUM_SHIFT_RIGHT: "shiftRightBy"
86 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy"
87 NUM_SUB_WRAP: "subWrap"
88 NUM_SUB_CHECKED: "subChecked"
89 NUM_MUL_WRAP: "mulWrap"
90 NUM_MUL_CHECKED: "mulChecked"
91 NUM_INT: "Int" imported
92 NUM_FLOAT: "Float" imported
93 NUM_AT_NATURAL: "@Natural"
94 NUM_NATURAL: "Natural" imported
95 NUM_NAT: "Nat" imported
96 NUM_INT_CAST: "intCast"
97 NUM_MAX_I128: "maxI128"
98 NUM_IS_MULTIPLE_OF: "isMultipleOf"
}
2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
@ -865,6 +880,11 @@ define_builtins! {
8 STR_STARTS_WITH: "startsWith"
9 STR_ENDS_WITH: "endsWith"
10 STR_FROM_INT: "fromInt"
11 STR_FROM_FLOAT: "fromFloat"
12 STR_FROM_UTF8: "fromUtf8"
13 STR_UT8_PROBLEM: "Utf8Problem" // the Utf8Problem type alias
14 STR_UT8_BYTE_PROBLEM: "Utf8ByteProblem" // the Utf8ByteProblem type alias
15 STR_TO_BYTES: "toBytes"
}
4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias
@ -888,39 +908,61 @@ define_builtins! {
18 LIST_SUM: "sum"
19 LIST_WALK: "walk"
20 LIST_LAST: "last"
21 LIST_KEEP_OKS: "keepOks"
22 LIST_KEEP_ERRS: "keepErrs"
23 LIST_MAP_WITH_INDEX: "mapWithIndex"
24 LIST_MAP2: "map2"
25 LIST_MAP3: "map3"
}
5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
1 RESULT_MAP: "map"
2 RESULT_MAP_ERR: "mapErr"
3 RESULT_WITH_DEFAULT: "withDefault"
4 RESULT_AFTER: "after"
}
6 DICT: "Dict" => {
0 DICT_DICT: "Dict" imported // the Dict.Dict type alias
1 DICT_AT_DICT: "@Dict" // the Dict.@Dict private tag
2 DICT_EMPTY: "empty"
3 DICT_SINGLETON: "singleton"
3 DICT_SINGLE: "single"
4 DICT_GET: "get"
5 DICT_INSERT: "insert"
6 DICT_LEN: "len"
5 DICT_GET_RESULT: "#get_result" // symbol used in the definition of Dict.get
6 DICT_WALK: "walk"
7 DICT_INSERT: "insert"
8 DICT_LEN: "len"
// This should not be exposed to users, its for testing the
// hash function ONLY
7 DICT_TEST_HASH: "hashTestOnly"
9 DICT_TEST_HASH: "hashTestOnly"
10 DICT_REMOVE: "remove"
11 DICT_CONTAINS: "contains"
12 DICT_KEYS: "keys"
13 DICT_VALUES: "values"
14 DICT_UNION: "union"
15 DICT_INTERSECTION: "intersection"
16 DICT_DIFFERENCE: "difference"
8 DICT_REMOVE: "remove"
9 DICT_CONTAINS: "contains"
10 DICT_KEYS: "keys"
11 DICT_VALUES: "values"
}
7 SET: "Set" => {
0 SET_SET: "Set" imported // the Set.Set type alias
1 SET_AT_SET: "@Set" // the Set.@Set private tag
2 SET_EMPTY: "empty"
3 SET_SINGLETON: "singleton"
4 SET_UNION: "union"
5 SET_FOLDL: "foldl"
6 SET_INSERT: "insert"
7 SET_REMOVE: "remove"
8 SET_DIFF: "diff"
3 SET_SINGLE: "single"
4 SET_LEN: "len"
5 SET_INSERT: "insert"
6 SET_REMOVE: "remove"
7 SET_UNION: "union"
8 SET_DIFFERENCE: "difference"
9 SET_INTERSECTION: "intersection"
10 SET_TO_LIST: "toList"
11 SET_FROM_LIST: "fromList"
12 SET_WALK: "walk"
13 SET_WALK_USER_FUNCTION: "#walk_user_function"
14 SET_CONTAINS: "contains"
}
num_modules: 8 // Keep this count up to date by hand! (TODO: see the mut_map! macro for how we could determine this count correctly in the macro)

View File

@ -12,7 +12,6 @@ roc_module = { path = "../module" }
roc_types = { path = "../types" }
roc_can = { path = "../can" }
roc_unify = { path = "../unify" }
roc_constrain = { path = "../constrain" }
roc_solve = { path = "../solve" }
roc_problem = { path = "../problem" }
ven_pretty = { path = "../../vendor/pretty" }
@ -21,7 +20,6 @@ ven_ena = { path = "../../vendor/ena" }
linked-hash-map = "0.5.4"
[dev-dependencies]
roc_constrain = { path = "../constrain" }
roc_load= { path = "../load" }
roc_builtins = { path = "../builtins" }
roc_parse = { path = "../parse" }

View File

@ -6,6 +6,13 @@ use roc_collections::all::{MutMap, MutSet};
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
fn should_borrow_layout(layout: &Layout) -> bool {
match layout {
Layout::Closure(_, _, _) => false,
_ => layout.is_refcounted(),
}
}
pub fn infer_borrow<'a>(
arena: &'a Bump,
procs: &MutMap<(Symbol, Layout<'a>), Proc<'a>>,
@ -14,8 +21,8 @@ pub fn infer_borrow<'a>(
items: MutMap::default(),
};
for proc in procs.values() {
param_map.visit_proc(arena, proc);
for (key, proc) in procs {
param_map.visit_proc(arena, proc, key.clone());
}
let mut env = BorrowInfState {
@ -39,8 +46,8 @@ pub fn infer_borrow<'a>(
// TODO in the future I think we need to do this properly, and group
// mutually recursive functions (or just make all their arguments owned)
for proc in procs.values() {
env.collect_proc(proc);
for (key, proc) in procs {
env.collect_proc(proc, key.1.clone());
}
if !env.modified {
@ -56,19 +63,19 @@ pub fn infer_borrow<'a>(
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum Key {
Declaration(Symbol),
pub enum Key<'a> {
Declaration(Symbol, Layout<'a>),
JoinPoint(JoinPointId),
}
#[derive(Debug, Clone, Default)]
pub struct ParamMap<'a> {
items: MutMap<Key, &'a [Param<'a>]>,
items: MutMap<Key<'a>, &'a [Param<'a>]>,
}
impl<'a> IntoIterator for ParamMap<'a> {
type Item = (Key, &'a [Param<'a>]);
type IntoIter = <std::collections::HashMap<Key, &'a [Param<'a>]> as IntoIterator>::IntoIter;
type Item = (Key<'a>, &'a [Param<'a>]);
type IntoIter = <std::collections::HashMap<Key<'a>, &'a [Param<'a>]> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.items.into_iter()
@ -76,8 +83,9 @@ impl<'a> IntoIterator for ParamMap<'a> {
}
impl<'a> IntoIterator for &'a ParamMap<'a> {
type Item = (&'a Key, &'a &'a [Param<'a>]);
type IntoIter = <&'a std::collections::HashMap<Key, &'a [Param<'a>]> as IntoIterator>::IntoIter;
type Item = (&'a Key<'a>, &'a &'a [Param<'a>]);
type IntoIter =
<&'a std::collections::HashMap<Key<'a>, &'a [Param<'a>]> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.items.iter()
@ -85,8 +93,8 @@ impl<'a> IntoIterator for &'a ParamMap<'a> {
}
impl<'a> ParamMap<'a> {
pub fn get_symbol(&self, symbol: Symbol) -> Option<&'a [Param<'a>]> {
let key = Key::Declaration(symbol);
pub fn get_symbol(&self, symbol: Symbol, layout: Layout<'a>) -> Option<&'a [Param<'a>]> {
let key = Key::Declaration(symbol, layout);
self.items.get(&key).copied()
}
@ -116,7 +124,7 @@ impl<'a> ParamMap<'a> {
fn init_borrow_args(arena: &'a Bump, ps: &'a [(Layout<'a>, Symbol)]) -> &'a [Param<'a>] {
Vec::from_iter_in(
ps.iter().map(|(layout, symbol)| Param {
borrow: layout.is_refcounted(),
borrow: should_borrow_layout(layout),
layout: layout.clone(),
symbol: *symbol,
}),
@ -125,11 +133,46 @@ impl<'a> ParamMap<'a> {
.into_bump_slice()
}
fn visit_proc(&mut self, arena: &'a Bump, proc: &Proc<'a>) {
self.items.insert(
Key::Declaration(proc.name),
fn init_borrow_args_always_owned(
arena: &'a Bump,
ps: &'a [(Layout<'a>, Symbol)],
) -> &'a [Param<'a>] {
Vec::from_iter_in(
ps.iter().map(|(layout, symbol)| Param {
borrow: false,
layout: layout.clone(),
symbol: *symbol,
}),
arena,
)
.into_bump_slice()
}
fn visit_proc(&mut self, arena: &'a Bump, proc: &Proc<'a>, key: (Symbol, Layout<'a>)) {
if proc.must_own_arguments {
self.visit_proc_always_owned(arena, proc, key);
return;
}
let already_in_there = self.items.insert(
Key::Declaration(proc.name, key.1),
Self::init_borrow_args(arena, proc.args),
);
debug_assert!(already_in_there.is_none());
self.visit_stmt(arena, proc.name, &proc.body);
}
fn visit_proc_always_owned(
&mut self,
arena: &'a Bump,
proc: &Proc<'a>,
key: (Symbol, Layout<'a>),
) {
let already_in_there = self.items.insert(
Key::Declaration(proc.name, key.1),
Self::init_borrow_args_always_owned(arena, proc.args),
);
debug_assert!(already_in_there.is_none());
self.visit_stmt(arena, proc.name, &proc.body);
}
@ -147,8 +190,10 @@ impl<'a> ParamMap<'a> {
remainder: v,
continuation: b,
} => {
self.items
let already_in_there = self
.items
.insert(Key::JoinPoint(*j), Self::init_borrow_params(arena, xs));
debug_assert!(already_in_there.is_none());
stack.push(v);
stack.push(b);
@ -211,7 +256,7 @@ impl<'a> BorrowInfState<'a> {
}
}
fn update_param_map(&mut self, k: Key) {
fn update_param_map(&mut self, k: Key<'a>) {
let arena = self.arena;
if let Some(ps) = self.param_map.items.get(&k) {
let ps = Vec::from_iter_in(
@ -318,14 +363,24 @@ impl<'a> BorrowInfState<'a> {
} = e;
match call_type {
ByName { name, .. } => {
ByName {
name, full_layout, ..
} => {
// get the borrow signature of the applied function
match self.param_map.get_symbol(*name) {
match self.param_map.get_symbol(*name, full_layout.clone()) {
Some(ps) => {
// the return value will be owned
self.own_var(z);
// if the function exects an owned argument (ps), the argument must be owned (args)
debug_assert_eq!(
arguments.len(),
ps.len(),
"{:?} has {} parameters, but was applied to {} arguments",
name,
ps.len(),
arguments.len()
);
self.own_args_using_params(arguments, ps);
}
None => {
@ -417,7 +472,12 @@ impl<'a> BorrowInfState<'a> {
match (v, b) {
(
Expr::Call(crate::ir::Call {
call_type: crate::ir::CallType::ByName { name: g, .. },
call_type:
crate::ir::CallType::ByName {
name: g,
full_layout,
..
},
arguments: ys,
..
}),
@ -425,7 +485,12 @@ impl<'a> BorrowInfState<'a> {
)
| (
Expr::Call(crate::ir::Call {
call_type: crate::ir::CallType::ByPointer { name: g, .. },
call_type:
crate::ir::CallType::ByPointer {
name: g,
full_layout,
..
},
arguments: ys,
..
}),
@ -434,7 +499,7 @@ impl<'a> BorrowInfState<'a> {
if self.current_proc == *g && x == *z {
// anonymous functions (for which the ps may not be known)
// can never be tail-recursive, so this is fine
if let Some(ps) = self.param_map.get_symbol(*g) {
if let Some(ps) = self.param_map.get_symbol(*g, full_layout.clone()) {
self.own_params_using_args(ys, ps)
}
}
@ -476,8 +541,10 @@ impl<'a> BorrowInfState<'a> {
Let(x, Expr::FunctionPointer(fsymbol, layout), _, b) => {
// ensure that the function pointed to is in the param map
if let Some(params) = self.param_map.get_symbol(*fsymbol) {
self.param_map.items.insert(Key::Declaration(*x), params);
if let Some(params) = self.param_map.get_symbol(*fsymbol, layout.clone()) {
self.param_map
.items
.insert(Key::Declaration(*x, layout.clone()), params);
}
self.collect_stmt(b);
@ -533,7 +600,7 @@ impl<'a> BorrowInfState<'a> {
}
}
fn collect_proc(&mut self, proc: &Proc<'a>) {
fn collect_proc(&mut self, proc: &Proc<'a>, layout: Layout<'a>) {
let old = self.param_set.clone();
let ys = Vec::from_iter_in(proc.args.iter().map(|t| t.1), self.arena).into_bump_slice();
@ -544,14 +611,16 @@ impl<'a> BorrowInfState<'a> {
self.owned.entry(proc.name).or_default();
self.collect_stmt(&proc.body);
self.update_param_map(Key::Declaration(proc.name));
self.update_param_map(Key::Declaration(proc.name, layout));
self.param_set = old;
}
}
pub fn foreign_borrow_signature(arena: &Bump, arity: usize) -> &[bool] {
let all = bumpalo::vec![in arena; false; arity];
// NOTE this means that Roc is responsible for cleaning up resources;
// the host cannot (currently) take ownership
let all = bumpalo::vec![in arena; true; arity];
all.into_bump_slice()
}
@ -573,36 +642,43 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListSet => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
ListSetInPlace => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
ListConcat | StrConcat => arena.alloc_slice_copy(&[owned, borrowed]),
ListConcat | StrConcat => arena.alloc_slice_copy(&[borrowed, borrowed]),
StrSplit => arena.alloc_slice_copy(&[borrowed, borrowed]),
ListSingle => arena.alloc_slice_copy(&[irrelevant]),
ListRepeat => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
ListRepeat => arena.alloc_slice_copy(&[irrelevant, borrowed]),
ListReverse => arena.alloc_slice_copy(&[owned]),
ListPrepend => arena.alloc_slice_copy(&[owned, owned]),
StrJoinWith => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
StrJoinWith => arena.alloc_slice_copy(&[borrowed, borrowed]),
ListJoin => arena.alloc_slice_copy(&[irrelevant]),
ListMap => arena.alloc_slice_copy(&[owned, irrelevant]),
ListKeepIf => arena.alloc_slice_copy(&[owned, irrelevant]),
ListMap | ListMapWithIndex => arena.alloc_slice_copy(&[owned, irrelevant]),
ListMap2 => arena.alloc_slice_copy(&[owned, owned, irrelevant]),
ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, irrelevant]),
ListKeepIf | ListKeepOks | ListKeepErrs => arena.alloc_slice_copy(&[owned, borrowed]),
ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]),
ListWalk => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]),
ListWalkBackwards => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]),
ListWalk => arena.alloc_slice_copy(&[owned, irrelevant, owned]),
ListWalkBackwards => arena.alloc_slice_copy(&[owned, irrelevant, owned]),
ListSum => arena.alloc_slice_copy(&[borrowed]),
// TODO when we have lists with capacity (if ever)
// List.append should own its first argument
ListAppend => arena.alloc_slice_copy(&[borrowed, owned]),
ListAppend => arena.alloc_slice_copy(&[owned, owned]),
Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap
| NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte
| NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt | NumBitwiseAnd
| NumBitwiseXor => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]),
And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap | NumSubChecked
| NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare
| NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf | NumPow | NumPowInt
| NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy
| NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor
| NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin => {
| NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin | NumIntCast => {
arena.alloc_slice_copy(&[irrelevant])
}
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]),
StrFromInt => arena.alloc_slice_copy(&[irrelevant]),
StrFromUtf8 => arena.alloc_slice_copy(&[owned]),
StrToBytes => arena.alloc_slice_copy(&[owned]),
StrFromInt | StrFromFloat => arena.alloc_slice_copy(&[irrelevant]),
Hash => arena.alloc_slice_copy(&[borrowed, irrelevant]),
DictSize => arena.alloc_slice_copy(&[borrowed]),
DictEmpty => &[],
@ -611,5 +687,11 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
DictContains => arena.alloc_slice_copy(&[borrowed, borrowed]),
DictGetUnsafe => arena.alloc_slice_copy(&[borrowed, borrowed]),
DictKeys | DictValues => arena.alloc_slice_copy(&[borrowed]),
DictUnion | DictDifference | DictIntersection => arena.alloc_slice_copy(&[owned, borrowed]),
// borrow function argument so we don't have to worry about RC of the closure
DictWalk => arena.alloc_slice_copy(&[owned, borrowed, owned]),
SetFromList => arena.alloc_slice_copy(&[owned]),
}
}

View File

@ -229,7 +229,11 @@ fn flatten<'a>(
tag_name,
layout,
} if union.alternatives.len() == 1
&& !matches!(layout, Layout::Union(UnionLayout::NullableWrapped { .. })| Layout::Union(UnionLayout::NullableUnwrapped { .. })) =>
&& !matches!(
layout,
Layout::Union(UnionLayout::NullableWrapped { .. })
| Layout::Union(UnionLayout::NullableUnwrapped { .. })
) =>
{
// TODO ^ do we need to check that guard.is_none() here?
@ -1149,7 +1153,7 @@ fn test_to_equality<'a>(
// (e.g. record pattern guard matches)
debug_assert!(union.alternatives.len() > 1);
let lhs = Expr::Literal(Literal::Int(tag_id as i64));
let lhs = Expr::Literal(Literal::Int(tag_id as i128));
let mut field_layouts =
bumpalo::collections::Vec::with_capacity_in(arguments.len(), env.arena);
@ -1185,7 +1189,7 @@ fn test_to_equality<'a>(
Test::IsInt(test_int) => {
// TODO don't downcast i128 here
debug_assert!(test_int <= i64::MAX as i128);
let lhs = Expr::Literal(Literal::Int(test_int as i64));
let lhs = Expr::Literal(Literal::Int(test_int as i128));
let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int64), lhs));

View File

@ -157,6 +157,24 @@ impl<'a, 'i> Env<'a, 'i> {
}
}
fn try_insert_struct_info(&mut self, symbol: Symbol, layout: &Layout<'a>) {
use Layout::*;
match layout {
Struct(fields) => {
self.constructor_map.insert(symbol, 0);
self.layout_map.insert(symbol, Layout::Struct(fields));
}
Closure(arguments, closure_layout, result) => {
let fpointer = Layout::FunctionPointer(arguments, result);
let fields = self.arena.alloc([fpointer, closure_layout.layout.clone()]);
self.constructor_map.insert(symbol, 0);
self.layout_map.insert(symbol, Layout::Struct(fields));
}
_ => {}
}
}
fn insert_struct_info(&mut self, symbol: Symbol, fields: &'a [Layout<'a>]) {
self.constructor_map.insert(symbol, 0);
self.layout_map.insert(symbol, Layout::Struct(fields));
@ -185,7 +203,7 @@ impl<'a, 'i> Env<'a, 'i> {
}
fn layout_for_constructor<'a>(
_arena: &'a Bump,
arena: &'a Bump,
layout: &Layout<'a>,
constructor: u64,
) -> ConstructorLayout<&'a [Layout<'a>]> {
@ -227,7 +245,12 @@ fn layout_for_constructor<'a>(
debug_assert_eq!(constructor, 0);
HasFields(fields)
}
_ => unreachable!(),
Closure(arguments, closure_layout, result) => {
let fpointer = Layout::FunctionPointer(arguments, result);
let fields = arena.alloc([fpointer, closure_layout.layout.clone()]);
HasFields(fields)
}
other => unreachable!("weird layout {:?}", other),
}
}
@ -253,11 +276,11 @@ fn work_for_constructor<'a>(
match layout_for_constructor(env.arena, full_layout, constructor) {
Unknown => Unknown,
IsNull => IsNull,
HasFields(cons_layout) => {
HasFields(constructor_layout) => {
// figure out if there is at least one aliased refcounted field. Only then
// does it make sense to inline the decrement
let at_least_one_aliased = (|| {
for (i, field_layout) in cons_layout.iter().enumerate() {
for (i, field_layout) in constructor_layout.iter().enumerate() {
if field_layout.contains_refcounted()
&& field_aliases.and_then(|map| map.get(&(i as u64))).is_some()
{
@ -269,7 +292,7 @@ fn work_for_constructor<'a>(
// for each field, if it has refcounted content, check if it has an alias
// if so, use the alias, otherwise load the field.
for (i, field_layout) in cons_layout.iter().enumerate() {
for (i, field_layout) in constructor_layout.iter().enumerate() {
if field_layout.contains_refcounted() {
match field_aliases.and_then(|map| map.get(&(i as u64))) {
Some(alias_symbol) => {
@ -283,7 +306,7 @@ fn work_for_constructor<'a>(
let expr = Expr::AccessAtIndex {
index: i as u64,
field_layouts: cons_layout,
field_layouts: constructor_layout,
structure: *symbol,
wrapped: Wrapped::MultiTagUnion,
};
@ -350,10 +373,11 @@ pub fn expand_and_cancel_proc<'a>(
introduced.push(*symbol);
}
Layout::Closure(_, closure_layout, _) => {
if let Layout::Struct(fields) = closure_layout.layout {
env.insert_struct_info(*symbol, fields);
}
Layout::Closure(arguments, closure_layout, result) => {
let fpointer = Layout::FunctionPointer(arguments, result);
let fields = env.arena.alloc([fpointer, closure_layout.layout.clone()]);
env.insert_struct_info(*symbol, fields);
introduced.push(*symbol);
}
_ => {}
}
@ -414,7 +438,10 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
match &expr {
Expr::AccessAtIndex {
structure, index, ..
structure,
index,
field_layouts,
..
} => {
let entry = env
.alias_map
@ -423,8 +450,14 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
entry.insert(*index, symbol);
// if the field is a struct, we know its constructor too!
let field_layout = &field_layouts[*index as usize];
env.try_insert_struct_info(symbol, field_layout);
new_cont = expand_and_cancel(env, cont);
env.remove_struct_info(symbol);
// make sure to remove the alias, so other branches don't use it by accident
env.alias_map
.get_mut(structure)

View File

@ -232,7 +232,7 @@ impl<'a> Context<'a> {
let mut vars = MutMap::default();
for (key, _) in param_map.into_iter() {
if let crate::borrow::Key::Declaration(symbol) = key {
if let crate::borrow::Key::Declaration(symbol, _) = key {
vars.insert(
*symbol,
VarInfo {
@ -466,9 +466,11 @@ impl<'a> Context<'a> {
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
ByName { name, .. } => {
ByName {
name, full_layout, ..
} => {
// get the borrow signature
match self.param_map.get_symbol(*name) {
match self.param_map.get_symbol(*name, full_layout.clone()) {
Some(ps) => {
let v = Expr::Call(crate::ir::Call {
call_type,
@ -601,7 +603,7 @@ impl<'a> Context<'a> {
persistent: bool,
consume: bool,
) -> Self {
// can this type be reference-counted at runtime?
// should we perform incs and decs on this value?
let reference = layout.contains_refcounted();
let info = VarInfo {
@ -727,8 +729,12 @@ impl<'a> Context<'a> {
layout,
} => {
// TODO this combines parts of Let and Switch. Did this happen correctly?
let mut case_live_vars = collect_stmt(stmt, &self.jp_live_vars, MutSet::default());
let mut case_live_vars = collect_stmt(pass, &self.jp_live_vars, MutSet::default());
case_live_vars.extend(collect_stmt(fail, &self.jp_live_vars, MutSet::default()));
// the result of an invoke should not be touched in the fail branch
// but it should be present in the pass branch (otherwise it would be dead)
debug_assert!(case_live_vars.contains(symbol));
case_live_vars.remove(symbol);
let fail = {
@ -756,9 +762,50 @@ impl<'a> Context<'a> {
layout: layout.clone(),
};
let stmt = self.arena.alloc(invoke);
let cont = self.arena.alloc(invoke);
(stmt, case_live_vars)
use crate::ir::CallType;
let stmt = match &call.call_type {
CallType::LowLevel { op } => {
let ps = crate::borrow::lowlevel_borrow_signature(self.arena, *op);
self.add_dec_after_lowlevel(call.arguments, ps, cont, &case_live_vars)
}
CallType::Foreign { .. } => {
let ps = crate::borrow::foreign_borrow_signature(
self.arena,
call.arguments.len(),
);
self.add_dec_after_lowlevel(call.arguments, ps, cont, &case_live_vars)
}
CallType::ByName {
name, full_layout, ..
} => {
// get the borrow signature
match self.param_map.get_symbol(*name, full_layout.clone()) {
Some(ps) => self.add_dec_after_application(
call.arguments,
ps,
cont,
&case_live_vars,
),
None => self.add_inc_before_consume_all(
call.arguments,
cont,
&case_live_vars,
),
}
}
CallType::ByPointer { .. } => {
self.add_inc_before_consume_all(call.arguments, cont, &case_live_vars)
}
};
let mut invoke_live_vars = case_live_vars;
occuring_variables_call(call, &mut invoke_live_vars);
(stmt, invoke_live_vars)
}
Join {
id: j,
@ -1002,10 +1049,15 @@ pub fn visit_declaration<'a>(
ctx.add_dec_for_dead_params(params, b, &b_live_vars)
}
pub fn visit_proc<'a>(arena: &'a Bump, param_map: &'a ParamMap<'a>, proc: &mut Proc<'a>) {
pub fn visit_proc<'a>(
arena: &'a Bump,
param_map: &'a ParamMap<'a>,
proc: &mut Proc<'a>,
layout: Layout<'a>,
) {
let ctx = Context::new(arena, param_map);
let params = match param_map.get_symbol(proc.name) {
let params = match param_map.get_symbol(proc.name, layout) {
Some(slice) => slice,
None => Vec::from_iter_in(
proc.args.iter().cloned().map(|(layout, symbol)| Param {

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
use crate::ir::Parens;
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_collections::all::{default_hasher, MutMap, MutSet};
@ -6,6 +7,7 @@ use roc_module::symbol::{Interns, Symbol};
use roc_types::subs::{Content, FlatType, Subs, Variable};
use roc_types::types::RecordField;
use std::collections::HashMap;
use ven_pretty::{DocAllocator, DocBuilder};
pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::<u8>() * 8) as usize;
const GENERATE_NULLABLE: bool = true;
@ -63,6 +65,34 @@ pub enum UnionLayout<'a> {
},
}
impl<'a> UnionLayout<'a> {
pub fn to_doc<'b, D, A>(&'b self, alloc: &'b D, _parens: Parens) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
use UnionLayout::*;
match self {
NonRecursive(tags) => {
let tags_doc = tags.iter().map(|fields| {
alloc.text("C ").append(alloc.intersperse(
fields.iter().map(|x| x.to_doc(alloc, Parens::InTypeParam)),
" ",
))
});
alloc
.text("[")
.append(alloc.intersperse(tags_doc, ", "))
.append(alloc.text("]"))
}
_ => alloc.text("TODO"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ClosureLayout<'a> {
/// the layout that this specific closure captures
@ -286,16 +316,16 @@ impl<'a> ClosureLayout<'a> {
&self,
original: Symbol,
symbols: &'a [Symbol],
) -> Result<crate::ir::Expr<'a>, Symbol> {
) -> BuildClosureData<'a> {
use crate::ir::Expr;
match self.layout {
Layout::Struct(fields) if fields.len() == 1 => Err(symbols[0]),
Layout::Struct(fields) if fields.len() == 1 => BuildClosureData::Alias(symbols[0]),
Layout::Struct(fields) => {
debug_assert!(fields.len() > 1);
debug_assert_eq!(fields.len(), symbols.len());
Ok(Expr::Struct(symbols))
BuildClosureData::Struct(Expr::Struct(symbols))
}
Layout::Union(UnionLayout::NonRecursive(tags)) => {
// NOTE it's very important that this Union consists of Closure tags
@ -307,20 +337,17 @@ impl<'a> ClosureLayout<'a> {
.position(|(tn, _)| *tn == TagName::Closure(original))
.unwrap() as _;
let expr = Expr::Tag {
BuildClosureData::Union {
tag_layout: Layout::Union(UnionLayout::NonRecursive(tags)),
tag_name: TagName::Closure(original),
tag_id,
union_size: tags.len() as u8,
arguments: symbols,
};
Ok(expr)
}
}
Layout::PhantomEmptyStruct => {
debug_assert_eq!(symbols.len(), 1);
Ok(Expr::Struct(&[]))
BuildClosureData::Struct(Expr::Struct(&[]))
}
_ => {
@ -332,12 +359,23 @@ impl<'a> ClosureLayout<'a> {
&self.layout
);
Err(symbols[0])
BuildClosureData::Alias(symbols[0])
}
}
}
}
pub enum BuildClosureData<'a> {
Alias(Symbol),
Struct(crate::ir::Expr<'a>),
Union {
tag_layout: Layout<'a>,
tag_name: TagName,
tag_id: u8,
union_size: u8,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
pub enum MemoryMode {
Unique,
@ -613,7 +651,10 @@ impl<'a> Layout<'a> {
Union(variant) => {
use UnionLayout::*;
matches!(variant, Recursive(_)| NullableWrapped { .. } | NullableUnwrapped { .. })
matches!(
variant,
Recursive(_) | NullableWrapped { .. } | NullableUnwrapped { .. }
)
}
RecursivePointer => true,
@ -654,6 +695,51 @@ impl<'a> Layout<'a> {
FunctionPointer(_, _) | Pointer(_) => false,
}
}
pub fn to_doc<'b, D, A>(&'b self, alloc: &'b D, parens: Parens) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
use Layout::*;
match self {
Builtin(builtin) => builtin.to_doc(alloc, parens),
PhantomEmptyStruct => alloc.text("{}"),
Struct(fields) => {
let fields_doc = fields.iter().map(|x| x.to_doc(alloc, parens));
alloc
.text("{")
.append(alloc.intersperse(fields_doc, ", "))
.append(alloc.text("}"))
}
Union(union_layout) => union_layout.to_doc(alloc, parens),
RecursivePointer => alloc.text("*self"),
FunctionPointer(args, result) => {
let args_doc = args.iter().map(|x| x.to_doc(alloc, Parens::InFunction));
alloc
.intersperse(args_doc, ", ")
.append(alloc.text(" -> "))
.append(result.to_doc(alloc, Parens::InFunction))
}
Closure(args, closure_layout, result) => {
let args_doc = args.iter().map(|x| x.to_doc(alloc, Parens::InFunction));
let bom = closure_layout.layout.to_doc(alloc, Parens::NotNeeded);
alloc
.intersperse(args_doc, ", ")
.append(alloc.text(" {| "))
.append(bom)
.append(" |} -> ")
.append(result.to_doc(alloc, Parens::InFunction))
}
Pointer(_) => todo!(),
}
}
}
/// Avoid recomputing Layout from Variable multiple times.
@ -794,7 +880,7 @@ impl<'a> Builtin<'a> {
/// Number of machine words in an empty one of these
pub const STR_WORDS: u32 = 2;
pub const DICT_WORDS: u32 = 6;
pub const DICT_WORDS: u32 = 3;
pub const SET_WORDS: u32 = Builtin::DICT_WORDS; // Set is an alias for Dict with {} for value
pub const LIST_WORDS: u32 = 2;
@ -878,6 +964,47 @@ impl<'a> Builtin<'a> {
Str | Dict(_, _) | Set(_) => true,
}
}
pub fn to_doc<'b, D, A>(&'b self, alloc: &'b D, _parens: Parens) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
use Builtin::*;
match self {
Int128 => alloc.text("Int128"),
Int64 => alloc.text("Int64"),
Int32 => alloc.text("Int32"),
Int16 => alloc.text("Int16"),
Int8 => alloc.text("Int8"),
Int1 => alloc.text("Int1"),
Usize => alloc.text("Usize"),
Float128 => alloc.text("Float128"),
Float64 => alloc.text("Float64"),
Float32 => alloc.text("Float32"),
Float16 => alloc.text("Float16"),
EmptyStr => alloc.text("EmptyStr"),
EmptyList => alloc.text("EmptyList"),
EmptyDict => alloc.text("EmptyDict"),
EmptySet => alloc.text("EmptySet"),
Str => alloc.text("Str"),
List(_, layout) => alloc
.text("List ")
.append(layout.to_doc(alloc, Parens::InTypeParam)),
Set(layout) => alloc
.text("Set ")
.append(layout.to_doc(alloc, Parens::InTypeParam)),
Dict(key_layout, value_layout) => alloc
.text("Dict ")
.append(key_layout.to_doc(alloc, Parens::InTypeParam))
.append(" ")
.append(value_layout.to_doc(alloc, Parens::InTypeParam)),
}
}
}
fn layout_from_flat_type<'a>(
@ -988,6 +1115,7 @@ fn layout_from_flat_type<'a>(
other => Ok(other),
}
}
Symbol::SET_SET => dict_layout_from_key_value(env, args[0], Variable::EMPTY_RECORD),
_ => {
panic!("TODO layout_from_flat_type for {:?}", Apply(symbol, args));
}
@ -1411,7 +1539,9 @@ pub fn union_sorted_tags_help<'a>(
}
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
// If we encounter an unbound type var (e.g. `Ok *`)
// then it's zero-sized; drop the argument.
// then it's zero-sized; In the future we may drop this argument
// completely, but for now we represent it with the empty struct
layouts.push(Layout::Struct(&[]))
}
Err(LayoutProblem::Erroneous) => {
// An erroneous type var will code gen to a runtime
@ -1488,7 +1618,9 @@ pub fn union_sorted_tags_help<'a>(
}
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
// If we encounter an unbound type var (e.g. `Ok *`)
// then it's zero-sized; drop the argument.
// then it's zero-sized; In the future we may drop this argument
// completely, but for now we represent it with the empty struct
arg_layouts.push(Layout::Struct(&[]));
}
Err(LayoutProblem::Erroneous) => {
// An erroneous type var will code gen to a runtime

View File

@ -646,13 +646,13 @@ mod test_mono {
let Test.4 = lowlevel DictEmpty ;
ret Test.4;
procedure Dict.6 (#Attr.2):
procedure Dict.8 (#Attr.2):
let Test.3 = lowlevel DictSize #Attr.2;
ret Test.3;
procedure Test.0 ():
let Test.2 = FunctionPointer Dict.2;
let Test.1 = CallByName Dict.6 Test.2;
let Test.1 = CallByName Dict.8 Test.2;
ret Test.1;
"#
),
@ -683,7 +683,6 @@ mod test_mono {
let Test.9 = 2i64;
let Test.4 = Array [Test.8, Test.9];
let Test.3 = CallByName Test.1 Test.4;
dec Test.4;
ret Test.3;
"#
),
@ -709,7 +708,6 @@ mod test_mono {
let Test.2 = Array [Test.5];
let Test.3 = 2i64;
let Test.1 = CallByName List.5 Test.2 Test.3;
dec Test.2;
ret Test.1;
"#
),
@ -1969,15 +1967,14 @@ mod test_mono {
let Test.7 = Index 1 Test.2;
let Test.8 = 0i64;
let Test.9 = Index 0 Test.7;
dec Test.7;
decref Test.2;
let Test.10 = lowlevel Eq Test.8 Test.9;
if Test.10 then
let Test.4 = Index 1 Test.2;
let Test.3 = 1i64;
decref Test.2;
ret Test.3;
else
let Test.5 = 0i64;
dec Test.2;
ret Test.5;
else
let Test.6 = 0i64;

View File

@ -118,6 +118,7 @@ pub enum Expr<'a> {
Closure(&'a [Loc<Pattern<'a>>], &'a Loc<Expr<'a>>),
/// Multiple defs in a row
Defs(&'a [&'a Loc<Def<'a>>], &'a Loc<Expr<'a>>),
Backpassing(&'a [Loc<Pattern<'a>>], &'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>),
// Application
/// To apply by name, do Apply(Var(...), ...)
@ -127,7 +128,7 @@ pub enum Expr<'a> {
UnaryOp(&'a Loc<Expr<'a>>, Loc<UnaryOp>),
// Conditionals
If(&'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>),
If(&'a [(Loc<Expr<'a>>, Loc<Expr<'a>>)], &'a Loc<Expr<'a>>),
When(
/// The condition
&'a Loc<Expr<'a>>,
@ -150,7 +151,7 @@ pub enum Expr<'a> {
Nested(&'a Expr<'a>),
// Problems
MalformedIdent(&'a str),
MalformedIdent(&'a str, crate::ident::BadIdent),
MalformedClosure,
// Both operators were non-associative, e.g. (True == False == False).
// We should tell the author to disambiguate by grouping them with parens.
@ -356,6 +357,7 @@ pub enum Pattern<'a> {
// Malformed
Malformed(&'a str),
MalformedIdent(&'a str, crate::ident::BadIdent),
QualifiedIdentifier {
module_name: &'a str,
ident: &'a str,
@ -411,7 +413,7 @@ impl<'a> Pattern<'a> {
}
}
Ident::AccessorFunction(string) => Pattern::Malformed(string),
Ident::Malformed(string) => Pattern::Malformed(string),
Ident::Malformed(string, _problem) => Pattern::Malformed(string),
}
}
@ -587,33 +589,6 @@ impl<'a> Spaceable<'a> for Def<'a> {
}
}
/// What we're currently attempting to parse, e.g.
/// "currently attempting to parse a list." This helps error messages!
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Attempting {
LineComment,
List,
Keyword,
StrLiteral,
RecordLiteral,
RecordFieldLabel,
InterpolatedString,
NumberLiteral,
UnicodeEscape,
ClosureParams,
ClosureBody,
Def,
Module,
Record,
Identifier,
HexDigit,
ConcreteType,
TypeVariable,
WhenCondition,
WhenBranch,
TODO,
}
impl<'a> Expr<'a> {
pub fn loc_ref(&'a self, region: Region) -> Loc<&'a Self> {
Loc {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,10 @@
use crate::ast::{CommentOrNewline, Spaceable, StrLiteral, TypeAnnotation};
use crate::blankspace::space0;
use crate::blankspace::space0_e;
use crate::ident::lowercase_ident;
use crate::module::package_name;
use crate::parser::{ascii_char, optional, Either, Parser, Progress::*, State, SyntaxError};
use crate::parser::Progress::{self, *};
use crate::parser::{
specialize, word1, EPackageEntry, EPackageName, EPackageOrPath, Parser, State,
};
use crate::string_literal;
use bumpalo::collections::Vec;
use inlinable_string::InlinableString;
@ -239,18 +241,32 @@ impl<'a> Spaceable<'a> for PackageEntry<'a> {
}
}
pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>, SyntaxError<'a>> {
pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>, EPackageEntry<'a>> {
move |arena, state| {
// You may optionally have a package shorthand,
// e.g. "uc" in `uc: roc/unicode 1.0.0`
//
// (Indirect dependencies don't have a shorthand.)
let (_, opt_shorthand, state) = optional(and!(
skip_second!(lowercase_ident(), ascii_char(b':')),
space0(1)
let min_indent = 1;
let (_, opt_shorthand, state) = maybe!(and!(
skip_second!(
specialize(|_, r, c| EPackageEntry::Shorthand(r, c), lowercase_ident()),
word1(b':', EPackageEntry::Colon)
),
space0_e(
min_indent,
EPackageEntry::Space,
EPackageEntry::IndentPackageOrPath
)
))
.parse(arena, state)?;
let (_, package_or_path, state) = loc!(specialize(
EPackageEntry::BadPackageOrPath,
package_or_path()
))
.parse(arena, state)?;
let (_, package_or_path, state) = loc!(package_or_path()).parse(arena, state)?;
let entry = match opt_shorthand {
Some((shorthand, spaces_after_shorthand)) => PackageEntry::Entry {
@ -269,24 +285,117 @@ pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>, SyntaxError<'a>>
}
}
pub fn package_or_path<'a>() -> impl Parser<'a, PackageOrPath<'a>, SyntaxError<'a>> {
map!(
either!(
string_literal::parse(),
and!(
package_name(),
skip_first!(one_or_more!(ascii_char(b' ')), package_version())
)
pub fn package_or_path<'a>() -> impl Parser<'a, PackageOrPath<'a>, EPackageOrPath<'a>> {
one_of![
map!(
specialize(EPackageOrPath::BadPath, string_literal::parse()),
PackageOrPath::Path
),
|answer| {
match answer {
Either::First(str_literal) => PackageOrPath::Path(str_literal),
Either::Second((name, version)) => PackageOrPath::Package(name, version),
}
}
)
map!(
and!(
specialize(EPackageOrPath::BadPackage, package_name()),
skip_first!(skip_spaces(), package_version())
),
|(name, version)| { PackageOrPath::Package(name, version) }
)
]
}
fn package_version<'a>() -> impl Parser<'a, Version<'a>, SyntaxError<'a>> {
fn skip_spaces<'a, T>() -> impl Parser<'a, (), T>
where
T: 'a,
{
|_, mut state: State<'a>| {
let mut chomped = 0;
let mut it = state.bytes.iter();
while let Some(b' ') = it.next() {
chomped += 1;
}
if chomped == 0 {
Ok((NoProgress, (), state))
} else {
state.column += chomped;
state.bytes = it.as_slice();
Ok((MadeProgress, (), state))
}
}
}
fn package_version<'a, T>() -> impl Parser<'a, Version<'a>, T>
where
T: 'a,
{
move |_, _| todo!("TODO parse package version")
}
#[inline(always)]
pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>, EPackageName> {
use encode_unicode::CharExt;
// e.g. rtfeldman/blah
//
// Package names and accounts can be capitalized and can contain dashes.
// They cannot contain underscores or other special characters.
// They must be ASCII.
|_, mut state: State<'a>| match chomp_package_part(state.bytes) {
Err(progress) => Err((
progress,
EPackageName::Account(state.line, state.column),
state,
)),
Ok(account) => {
let mut chomped = account.len();
if let Ok(('/', width)) = char::from_utf8_slice_start(&state.bytes[chomped..]) {
chomped += width;
match chomp_package_part(&state.bytes[chomped..]) {
Err(progress) => Err((
progress,
EPackageName::Pkg(state.line, state.column + chomped as u16),
state,
)),
Ok(pkg) => {
chomped += pkg.len();
state.column += chomped as u16;
state.bytes = &state.bytes[chomped..];
let value = PackageName { account, pkg };
Ok((MadeProgress, value, state))
}
}
} else {
Err((
MadeProgress,
EPackageName::MissingSlash(state.line, state.column + chomped as u16),
state,
))
}
}
}
}
fn chomp_package_part(buffer: &[u8]) -> Result<&str, Progress> {
use encode_unicode::CharExt;
let mut chomped = 0;
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
if ch == '-' || ch.is_ascii_alphanumeric() {
chomped += width;
} else {
// we're done
break;
}
}
if chomped == 0 {
Err(Progress::NoProgress)
} else {
let name = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
Ok(name)
}
}

View File

@ -1,12 +1,7 @@
use crate::ast::Attempting;
use crate::keyword;
use crate::parser::Progress::{self, *};
use crate::parser::{peek_utf8_char, unexpected, ParseResult, Parser, State, SyntaxError};
use bumpalo::collections::string::String;
use crate::parser::{BadInputError, Col, EExpr, ParseResult, Parser, Row, State};
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use roc_collections::all::arena_join;
use roc_region::all::Region;
/// The parser accepts all of these in any position where any one of them could
/// appear. This way, canonicalization can give more helpful error messages like
@ -23,10 +18,10 @@ pub enum Ident<'a> {
module_name: &'a str,
parts: &'a [&'a str],
},
/// .foo
/// .foo { foo: 42 }
AccessorFunction(&'a str),
/// .Foo or foo. or something like foo.Bar
Malformed(&'a str),
Malformed(&'a str, BadIdent),
}
impl<'a> Ident<'a> {
@ -50,7 +45,7 @@ impl<'a> Ident<'a> {
len - 1
}
AccessorFunction(string) => string.len(),
Malformed(string) => string.len(),
Malformed(string, _) => string.len(),
}
}
@ -59,349 +54,43 @@ impl<'a> Ident<'a> {
}
}
/// Parse an identifier into a string.
///
/// This is separate from the `ident` Parser because string interpolation
/// wants to use it this way.
///
/// By design, this does not check for reserved keywords like "if", "else", etc.
/// Sometimes we may want to check for those later in the process, and give
/// more contextually-aware error messages than "unexpected `if`" or the like.
#[inline(always)]
pub fn parse_ident<'a>(
arena: &'a Bump,
mut state: State<'a>,
) -> ParseResult<'a, (Ident<'a>, Option<char>), SyntaxError<'a>> {
let mut part_buf = String::new_in(arena); // The current "part" (parts are dot-separated.)
let mut capitalized_parts: Vec<&'a str> = Vec::new_in(arena);
let mut noncapitalized_parts: Vec<&'a str> = Vec::new_in(arena);
let mut is_capitalized;
let is_accessor_fn;
let mut is_private_tag = false;
let start_bytes_len = state.bytes.len();
// Identifiers and accessor functions must start with either a letter or a dot.
// If this starts with neither, it must be something else!
match peek_utf8_char(&state) {
Ok((first_ch, bytes_parsed)) => {
if first_ch.is_alphabetic() {
part_buf.push(first_ch);
is_capitalized = first_ch.is_uppercase();
is_accessor_fn = false;
state = state.advance_without_indenting(arena, bytes_parsed)?;
} else if first_ch == '.' {
is_capitalized = false;
is_accessor_fn = true;
state = state.advance_without_indenting(arena, bytes_parsed)?;
} else if first_ch == '@' {
state = state.advance_without_indenting(arena, bytes_parsed)?;
// '@' must always be followed by a capital letter!
match peek_utf8_char(&state) {
Ok((next_ch, next_bytes_parsed)) => {
if next_ch.is_uppercase() {
state = state.advance_without_indenting(arena, next_bytes_parsed)?;
part_buf.push('@');
part_buf.push(next_ch);
is_private_tag = true;
is_capitalized = true;
is_accessor_fn = false;
} else {
return Err(unexpected(
arena,
bytes_parsed + next_bytes_parsed,
Attempting::Identifier,
state,
));
}
}
Err(reason) => {
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
return state.fail(arena, progress, reason);
}
}
} else {
return Err(unexpected(arena, 0, Attempting::Identifier, state));
}
}
Err(reason) => {
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
return state.fail(arena, progress, reason);
}
}
while !state.bytes.is_empty() {
match peek_utf8_char(&state) {
Ok((ch, bytes_parsed)) => {
// After the first character, only these are allowed:
//
// * Unicode alphabetic chars - you might name a variable `鹏` if that's clear to your readers
// * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric()
// * A dot ('.')
if ch.is_alphabetic() {
if part_buf.is_empty() {
// Capitalization is determined by the first character in the part.
is_capitalized = ch.is_uppercase();
}
part_buf.push(ch);
} else if ch.is_ascii_digit() {
// Parts may not start with numbers!
if part_buf.is_empty() {
return malformed(
Some(ch),
arena,
state,
capitalized_parts,
noncapitalized_parts,
);
}
part_buf.push(ch);
} else if ch == '.' {
// There are two posssible errors here:
//
// 1. Having two consecutive dots is an error.
// 2. Having capitalized parts after noncapitalized (e.g. `foo.Bar`) is an error.
if part_buf.is_empty() || (is_capitalized && !noncapitalized_parts.is_empty()) {
return malformed(
Some(ch),
arena,
state,
capitalized_parts,
noncapitalized_parts,
);
}
if is_capitalized {
capitalized_parts.push(part_buf.into_bump_str());
} else {
noncapitalized_parts.push(part_buf.into_bump_str());
}
// Now that we've recorded the contents of the current buffer, reset it.
part_buf = String::new_in(arena);
} else {
// This must be the end of the identifier. We're done!
break;
}
state = state.advance_without_indenting(arena, bytes_parsed)?;
}
Err(reason) => {
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
return state.fail(arena, progress, reason);
}
}
}
if part_buf.is_empty() {
// We probably had a trailing dot, e.g. `Foo.bar.` - this is malformed!
//
// This condition might also occur if we encounter a malformed accessor like `.|`
//
// If we made it this far and don't have a next_char, then necessarily
// we have consumed a '.' char previously.
return malformed(
Some('.'),
arena,
state,
capitalized_parts,
noncapitalized_parts,
);
}
// Record the final parts.
if is_capitalized {
capitalized_parts.push(part_buf.into_bump_str());
} else {
noncapitalized_parts.push(part_buf.into_bump_str());
}
let answer = if is_accessor_fn {
// Handle accessor functions first because they have the strictest requirements.
// Accessor functions may have exactly 1 noncapitalized part, and no capitalzed parts.
if capitalized_parts.is_empty() && noncapitalized_parts.len() == 1 && !is_private_tag {
let value = noncapitalized_parts.iter().next().unwrap();
Ident::AccessorFunction(value)
} else {
return malformed(None, arena, state, capitalized_parts, noncapitalized_parts);
}
} else if noncapitalized_parts.is_empty() {
// We have capitalized parts only, so this must be a tag.
match capitalized_parts.first() {
Some(value) => {
if capitalized_parts.len() == 1 {
if is_private_tag {
Ident::PrivateTag(value)
} else {
Ident::GlobalTag(value)
}
} else {
// This is a qualified tag, which is not allowed!
return malformed(None, arena, state, capitalized_parts, noncapitalized_parts);
}
}
None => {
// We had neither capitalized nor noncapitalized parts,
// yet we made it this far. The only explanation is that this was
// a stray '.' drifting through the cosmos.
return Err(unexpected(arena, 1, Attempting::Identifier, state));
}
}
} else if is_private_tag {
// This is qualified field access with an '@' in front, which does not make sense!
return malformed(None, arena, state, capitalized_parts, noncapitalized_parts);
} else {
// We have multiple noncapitalized parts, so this must be field access.
Ident::Access {
module_name: join_module_parts(arena, capitalized_parts.into_bump_slice()),
parts: noncapitalized_parts.into_bump_slice(),
}
};
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
debug_assert_eq!(progress, Progress::MadeProgress,);
Ok((Progress::MadeProgress, (answer, None), state))
}
fn malformed<'a>(
opt_bad_char: Option<char>,
arena: &'a Bump,
mut state: State<'a>,
capitalized_parts: Vec<&'a str>,
noncapitalized_parts: Vec<&'a str>,
) -> ParseResult<'a, (Ident<'a>, Option<char>), SyntaxError<'a>> {
// Reconstruct the original string that we've been parsing.
let mut full_string = String::new_in(arena);
full_string
.push_str(arena_join(arena, &mut capitalized_parts.into_iter(), ".").into_bump_str());
full_string
.push_str(arena_join(arena, &mut noncapitalized_parts.into_iter(), ".").into_bump_str());
if let Some(bad_char) = opt_bad_char {
full_string.push(bad_char);
}
// Consume the remaining chars in the identifier.
let mut next_char = None;
while !state.bytes.is_empty() {
match peek_utf8_char(&state) {
Ok((ch, bytes_parsed)) => {
// We can't use ch.is_alphanumeric() here because that passes for
// things that are "numeric" but not ASCII digits, like `¾`
if ch == '.' || ch.is_alphabetic() || ch.is_ascii_digit() {
full_string.push(ch);
} else {
next_char = Some(ch);
break;
}
state = state.advance_without_indenting(arena, bytes_parsed)?;
}
Err(reason) => return state.fail(arena, MadeProgress, reason),
}
}
Ok((
MadeProgress,
(Ident::Malformed(full_string.into_bump_str()), next_char),
state,
))
}
pub fn ident<'a>() -> impl Parser<'a, Ident<'a>, SyntaxError<'a>> {
move |arena: &'a Bump, state: State<'a>| {
// Discard next_char; we don't need it.
let (progress, (string, _), state) = parse_ident(arena, state)?;
Ok((progress, string, state))
}
}
pub fn global_tag_or_ident<'a, F>(pred: F) -> impl Parser<'a, &'a str, SyntaxError<'a>>
where
F: Fn(char) -> bool,
{
move |arena, mut state: State<'a>| {
// pred will determine if this is a tag or ident (based on capitalization)
let (first_letter, bytes_parsed) = match peek_utf8_char(&state) {
Ok((first_letter, bytes_parsed)) => {
if !pred(first_letter) {
return Err(unexpected(arena, 0, Attempting::RecordFieldLabel, state));
}
(first_letter, bytes_parsed)
}
Err(reason) => return state.fail(arena, NoProgress, reason),
};
let mut buf = String::with_capacity_in(1, arena);
buf.push(first_letter);
state = state.advance_without_indenting(arena, bytes_parsed)?;
while !state.bytes.is_empty() {
match peek_utf8_char(&state) {
Ok((ch, bytes_parsed)) => {
// After the first character, only these are allowed:
//
// * Unicode alphabetic chars - you might include `鹏` if that's clear to your readers
// * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric()
// * A ':' indicating the end of the field
if ch.is_alphabetic() || ch.is_ascii_digit() {
buf.push(ch);
state = state.advance_without_indenting(arena, bytes_parsed)?;
} else {
// This is the end of the field. We're done!
break;
}
}
Err(reason) => return state.fail(arena, MadeProgress, reason),
};
}
Ok((MadeProgress, buf.into_bump_str(), state))
}
}
/// This could be:
///
/// * A record field, e.g. "email" in `.email` or in `email:`
/// * A named pattern match, e.g. "foo" in `foo =` or `foo ->` or `\foo ->`
pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> {
pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, ()> {
move |_, state: State<'a>| match chomp_lowercase_part(state.bytes) {
Err(progress) => Err((progress, (), state)),
Ok(ident) => {
if crate::keyword::KEYWORDS.iter().any(|kw| &ident == kw) {
Err((NoProgress, (), state))
} else {
let width = ident.len();
match state.advance_without_indenting_ee(width, |_, _| ()) {
Ok(state) => Ok((MadeProgress, ident, state)),
Err(bad) => Err(bad),
}
}
}
}
}
pub fn tag_name<'a>() -> impl Parser<'a, &'a str, ()> {
move |arena, state: State<'a>| {
let (progress, ident, state) =
global_tag_or_ident(|first_char| first_char.is_lowercase()).parse(arena, state)?;
// to parse a valid ident, progress must be made
debug_assert_eq!(progress, MadeProgress);
if (ident == keyword::IF)
|| (ident == keyword::THEN)
|| (ident == keyword::ELSE)
|| (ident == keyword::WHEN)
|| (ident == keyword::IS)
|| (ident == keyword::AS)
{
// TODO Calculate the correct region based on state
let region = Region::zero();
Err((MadeProgress, SyntaxError::ReservedKeyword(region), state))
if state.bytes.starts_with(b"@") {
match chomp_private_tag(state.bytes, state.line, state.column) {
Err(BadIdent::Start(_, _)) => Err((NoProgress, (), state)),
Err(_) => Err((MadeProgress, (), state)),
Ok(ident) => {
let width = ident.len();
match state.advance_without_indenting_ee(width, |_, _| ()) {
Ok(state) => Ok((MadeProgress, ident, state)),
Err(bad) => Err(bad),
}
}
}
} else {
Ok((MadeProgress, ident, state))
uppercase_ident().parse(arena, state)
}
}
}
@ -411,28 +100,445 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> {
/// * A module name
/// * A type name
/// * A global tag
pub fn uppercase_ident<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> {
global_tag_or_ident(|first_char| first_char.is_uppercase())
}
pub fn unqualified_ident<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> {
global_tag_or_ident(|first_char| first_char.is_alphabetic())
}
pub fn join_module_parts<'a>(arena: &'a Bump, module_parts: &[&str]) -> &'a str {
let capacity = module_parts.len() * 3; // Module parts tend to be 3+ characters.
let mut buf = String::with_capacity_in(capacity, arena);
let mut any_parts_added = false;
for part in module_parts {
if any_parts_added {
buf.push('.');
} else {
any_parts_added = true;
pub fn uppercase_ident<'a>() -> impl Parser<'a, &'a str, ()> {
move |_, state: State<'a>| match chomp_uppercase_part(state.bytes) {
Err(progress) => Err((progress, (), state)),
Ok(ident) => {
let width = ident.len();
match state.advance_without_indenting_ee(width, |_, _| ()) {
Ok(state) => Ok((MadeProgress, ident, state)),
Err(bad) => Err(bad),
}
}
}
}
buf.push_str(part);
pub fn unqualified_ident<'a>() -> impl Parser<'a, &'a str, ()> {
move |_, state: State<'a>| match chomp_part(|c| c.is_alphabetic(), state.bytes) {
Err(progress) => Err((progress, (), state)),
Ok(ident) => {
if crate::keyword::KEYWORDS.iter().any(|kw| &ident == kw) {
Err((MadeProgress, (), state))
} else {
let width = ident.len();
match state.advance_without_indenting_ee(width, |_, _| ()) {
Ok(state) => Ok((MadeProgress, ident, state)),
Err(bad) => Err(bad),
}
}
}
}
}
macro_rules! advance_state {
($state:expr, $n:expr) => {
$state.advance_without_indenting_ee($n, |r, c| {
BadIdent::Space(crate::parser::BadInputError::LineTooLong, r, c)
})
};
}
pub fn parse_ident_help<'a>(
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Ident<'a>, EExpr<'a>> {
let initial = state;
match parse_ident_help_help(arena, state) {
Ok((progress, ident, state)) => {
if let Ident::Access { module_name, parts } = ident {
if module_name.is_empty() {
if let Some(first) = parts.first() {
for keyword in crate::keyword::KEYWORDS.iter() {
if first == keyword {
return Err((
NoProgress,
EExpr::Start(initial.line, initial.column),
initial,
));
}
}
}
}
}
Ok((progress, ident, state))
}
Err((NoProgress, _, state)) => {
Err((NoProgress, EExpr::Start(state.line, state.column), state))
}
Err((MadeProgress, fail, state)) => match fail {
BadIdent::Start(r, c) => Err((NoProgress, EExpr::Start(r, c), state)),
BadIdent::Space(e, r, c) => Err((NoProgress, EExpr::Space(e, r, c), state)),
_ => malformed_identifier(initial.bytes, fail, state),
},
}
}
fn malformed_identifier<'a>(
initial_bytes: &'a [u8],
problem: BadIdent,
mut state: State<'a>,
) -> ParseResult<'a, Ident<'a>, EExpr<'a>> {
let chomped = chomp_malformed(state.bytes);
let delta = initial_bytes.len() - state.bytes.len();
let parsed_str = unsafe { std::str::from_utf8_unchecked(&initial_bytes[..chomped + delta]) };
state = state.advance_without_indenting_ee(chomped, |r, c| {
EExpr::Space(crate::parser::BadInputError::LineTooLong, r, c)
})?;
Ok((MadeProgress, Ident::Malformed(parsed_str, problem), state))
}
/// skip forward to the next non-identifier character
pub fn chomp_malformed(bytes: &[u8]) -> usize {
use encode_unicode::CharExt;
let mut chomped = 0;
while let Ok((ch, width)) = char::from_utf8_slice_start(&bytes[chomped..]) {
// We can't use ch.is_alphanumeric() here because that passes for
// things that are "numeric" but not ASCII digits, like `¾`
if ch == '.' || ch == '_' || ch.is_alphabetic() || ch.is_ascii_digit() {
chomped += width;
continue;
} else {
break;
}
}
buf.into_bump_str()
chomped
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BadIdent {
Start(Row, Col),
Space(BadInputError, Row, Col),
Underscore(Row, Col),
QualifiedTag(Row, Col),
WeirdAccessor(Row, Col),
WeirdDotAccess(Row, Col),
WeirdDotQualified(Row, Col),
StrayDot(Row, Col),
BadPrivateTag(Row, Col),
}
fn chomp_lowercase_part(buffer: &[u8]) -> Result<&str, Progress> {
chomp_part(|c: char| c.is_lowercase(), buffer)
}
fn chomp_uppercase_part(buffer: &[u8]) -> Result<&str, Progress> {
chomp_part(|c: char| c.is_uppercase(), buffer)
}
#[inline(always)]
fn chomp_part<F>(leading_is_good: F, buffer: &[u8]) -> Result<&str, Progress>
where
F: Fn(char) -> bool,
{
use encode_unicode::CharExt;
let mut chomped = 0;
if let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
if leading_is_good(ch) {
chomped += width;
} else {
return Err(NoProgress);
}
}
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
if ch.is_alphabetic() || ch.is_ascii_digit() {
chomped += width;
} else {
// we're done
break;
}
}
if chomped == 0 {
Err(NoProgress)
} else {
let name = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
Ok(name)
}
}
/// a `.foo` accessor function
fn chomp_accessor(buffer: &[u8], row: Row, col: Col) -> Result<&str, BadIdent> {
// assumes the leading `.` has been chomped already
use encode_unicode::CharExt;
match chomp_lowercase_part(buffer) {
Ok(name) => {
let chomped = name.len();
if let Ok(('.', _)) = char::from_utf8_slice_start(&buffer[chomped..]) {
Err(BadIdent::WeirdAccessor(row, col))
} else {
Ok(name)
}
}
Err(_) => {
// we've already made progress with the initial `.`
Err(BadIdent::StrayDot(row, col + 1))
}
}
}
/// a `@Token` private tag
fn chomp_private_tag(buffer: &[u8], row: Row, col: Col) -> Result<&str, BadIdent> {
// assumes the leading `@` has NOT been chomped already
debug_assert_eq!(buffer.get(0), Some(&b'@'));
use encode_unicode::CharExt;
match chomp_uppercase_part(&buffer[1..]) {
Ok(name) => {
let width = 1 + name.len();
if let Ok(('.', _)) = char::from_utf8_slice_start(&buffer[width..]) {
Err(BadIdent::BadPrivateTag(row, col + width as u16))
} else {
let value = unsafe { std::str::from_utf8_unchecked(&buffer[..width]) };
Ok(value)
}
}
Err(_) => Err(BadIdent::BadPrivateTag(row, col + 1)),
}
}
fn chomp_identifier_chain<'a>(
arena: &'a Bump,
buffer: &'a [u8],
row: Row,
col: Col,
) -> Result<(u16, Ident<'a>), (u16, BadIdent)> {
use encode_unicode::CharExt;
let first_is_uppercase;
let mut chomped = 0;
match char::from_utf8_slice_start(&buffer[chomped..]) {
Ok((ch, width)) => match ch {
'.' => match chomp_accessor(&buffer[1..], row, col) {
Ok(accessor) => {
let bytes_parsed = 1 + accessor.len();
return Ok((bytes_parsed as u16, Ident::AccessorFunction(accessor)));
}
Err(fail) => return Err((1, fail)),
},
'@' => match chomp_private_tag(buffer, row, col) {
Ok(tagname) => {
let bytes_parsed = tagname.len();
return Ok((bytes_parsed as u16, Ident::PrivateTag(tagname)));
}
Err(fail) => return Err((1, fail)),
},
c if c.is_alphabetic() => {
// fall through
chomped += width;
first_is_uppercase = c.is_uppercase();
}
_ => {
return Err((0, BadIdent::Start(row, col)));
}
},
Err(_) => return Err((0, BadIdent::Start(row, col))),
}
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
if ch.is_alphabetic() || ch.is_ascii_digit() {
chomped += width;
} else {
// we're done
break;
}
}
if let Ok(('.', _)) = char::from_utf8_slice_start(&buffer[chomped..]) {
let module_name = if first_is_uppercase {
match chomp_module_chain(&buffer[chomped..]) {
Ok(width) => {
chomped += width as usize;
unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }
}
Err(MadeProgress) => todo!(),
Err(NoProgress) => unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) },
}
} else {
""
};
let mut parts = Vec::with_capacity_in(4, arena);
if !first_is_uppercase {
let first_part = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
parts.push(first_part);
}
match chomp_access_chain(&buffer[chomped..], &mut parts) {
Ok(width) => {
chomped += width as usize;
let ident = Ident::Access {
module_name,
parts: parts.into_bump_slice(),
};
Ok((chomped as u16, ident))
}
Err(0) if !module_name.is_empty() => Err((
chomped as u16,
BadIdent::QualifiedTag(row, chomped as u16 + col),
)),
Err(1) if parts.is_empty() => Err((
chomped as u16 + 1,
BadIdent::WeirdDotQualified(row, chomped as u16 + col + 1),
)),
Err(width) => Err((
chomped as u16 + width,
BadIdent::WeirdDotAccess(row, chomped as u16 + col + width),
)),
}
} else if let Ok(('_', _)) = char::from_utf8_slice_start(&buffer[chomped..]) {
// we don't allow underscores in the middle of an identifier
// but still parse them (and generate a malformed identifier)
// to give good error messages for this case
Err((
chomped as u16 + 1,
BadIdent::Underscore(row, col + chomped as u16 + 1),
))
} else if first_is_uppercase {
// just one segment, starting with an uppercase letter; that's a global tag
let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
Ok((chomped as u16, Ident::GlobalTag(value)))
} else {
// just one segment, starting with a lowercase letter; that's a normal identifier
let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
let ident = Ident::Access {
module_name: "",
parts: arena.alloc([value]),
};
Ok((chomped as u16, ident))
}
}
fn chomp_module_chain(buffer: &[u8]) -> Result<u16, Progress> {
let mut chomped = 0;
while let Some(b'.') = buffer.get(chomped) {
match &buffer.get(chomped + 1..) {
Some(slice) => match chomp_uppercase_part(slice) {
Ok(name) => {
chomped += name.len() + 1;
}
Err(MadeProgress) => return Err(MadeProgress),
Err(NoProgress) => break,
},
None => return Err(MadeProgress),
}
}
if chomped == 0 {
Err(NoProgress)
} else {
Ok(chomped as u16)
}
}
pub fn concrete_type<'a>() -> impl Parser<'a, (&'a str, &'a str), ()> {
move |_, state: State<'a>| match chomp_concrete_type(state.bytes) {
Err(progress) => Err((progress, (), state)),
Ok((module_name, type_name, width)) => {
match state.advance_without_indenting_ee(width, |_, _| ()) {
Ok(state) => Ok((MadeProgress, (module_name, type_name), state)),
Err(bad) => Err(bad),
}
}
}
}
// parse a type name like `Result` or `Result.Result`
fn chomp_concrete_type(buffer: &[u8]) -> Result<(&str, &str, usize), Progress> {
let first = crate::ident::chomp_uppercase_part(buffer)?;
if let Some(b'.') = buffer.get(first.len()) {
match crate::ident::chomp_module_chain(&buffer[first.len()..]) {
Err(_) => Err(MadeProgress),
Ok(rest) => {
let width = first.len() + rest as usize;
// we must explicitly check here for a trailing `.`
if let Some(b'.') = buffer.get(width) {
return Err(MadeProgress);
}
let slice = &buffer[..width];
match slice.iter().rev().position(|c| *c == b'.') {
None => Ok(("", first, first.len())),
Some(rev_index) => {
let index = slice.len() - rev_index;
let module_name =
unsafe { std::str::from_utf8_unchecked(&slice[..index - 1]) };
let type_name = unsafe { std::str::from_utf8_unchecked(&slice[index..]) };
Ok((module_name, type_name, width))
}
}
}
}
} else {
Ok(("", first, first.len()))
}
}
fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, &'a str>) -> Result<u16, u16> {
let mut chomped = 0;
while let Some(b'.') = buffer.get(chomped) {
match &buffer.get(chomped + 1..) {
Some(slice) => match chomp_lowercase_part(slice) {
Ok(name) => {
let value = unsafe {
std::str::from_utf8_unchecked(
&buffer[chomped + 1..chomped + 1 + name.len()],
)
};
parts.push(value);
chomped += name.len() + 1;
}
Err(_) => return Err(chomped as u16 + 1),
},
None => return Err(chomped as u16 + 1),
}
}
if chomped == 0 {
Err(0)
} else {
Ok(chomped as u16)
}
}
fn parse_ident_help_help<'a>(
arena: &'a Bump,
mut state: State<'a>,
) -> ParseResult<'a, Ident<'a>, BadIdent> {
match chomp_identifier_chain(arena, state.bytes, state.line, state.column) {
Ok((width, ident)) => {
state = advance_state!(state, width as usize)?;
Ok((MadeProgress, ident, state))
}
Err((0, fail)) => Err((NoProgress, fail, state)),
Err((width, fail)) => {
state = advance_state!(state, width as usize)?;
Err((MadeProgress, fail, state))
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,189 +1,163 @@
use crate::ast::{Attempting, Base, Expr};
use crate::parser::{
parse_utf8, unexpected, unexpected_eof, ParseResult, Parser, Progress, State, SyntaxError,
};
use bumpalo::Bump;
use std::char;
use std::str::from_utf8_unchecked;
use crate::ast::Base;
use crate::parser::{Number, ParseResult, Parser, Progress, State};
pub fn number_literal<'a>() -> impl Parser<'a, Expr<'a>, SyntaxError<'a>> {
move |arena, state: State<'a>| {
let bytes = &mut state.bytes.iter();
pub enum NumLiteral<'a> {
Float(&'a str),
Num(&'a str),
NonBase10Int {
string: &'a str,
base: Base,
is_negative: bool,
},
}
match bytes.next() {
Some(&first_byte) => {
// Number literals must start with either an '-' or a digit.
if first_byte == b'-' || (first_byte as char).is_ascii_digit() {
parse_number_literal(first_byte as char, bytes, arena, state)
} else {
Err(unexpected(arena, 1, Attempting::NumberLiteral, state))
}
pub fn positive_number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, Number> {
move |_arena, state: State<'a>| {
match state.bytes.get(0) {
Some(first_byte) if (*first_byte as char).is_ascii_digit() => {
parse_number_base(false, &state.bytes, state)
}
_ => {
// this is not a number at all
Err((Progress::NoProgress, Number::End, state))
}
None => Err(unexpected_eof(arena, state, 0)),
}
}
}
#[inline(always)]
fn parse_number_literal<'a, I>(
first_ch: char,
bytes: &mut I,
arena: &'a Bump,
pub fn number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, Number> {
move |_arena, state: State<'a>| {
match state.bytes.get(0) {
Some(first_byte) if *first_byte == b'-' => {
// drop the minus
parse_number_base(true, &state.bytes[1..], state)
}
Some(first_byte) if (*first_byte as char).is_ascii_digit() => {
parse_number_base(false, &state.bytes, state)
}
_ => {
// this is not a number at all
Err((Progress::NoProgress, Number::End, state))
}
}
}
}
fn parse_number_base<'a>(
is_negated: bool,
bytes: &'a [u8],
state: State<'a>,
) -> ParseResult<'a, Expr<'a>, SyntaxError<'a>>
where
I: Iterator<Item = &'a u8>,
{
use self::LiteralType::*;
let mut typ = Num;
// We already parsed 1 character (which may have been a minus sign).
let mut bytes_parsed = 1;
let mut prev_byte = first_ch as u8;
let mut has_parsed_digits = first_ch.is_ascii_digit();
for &next_byte in bytes {
let err_unexpected = || {
Err(unexpected(
arena,
bytes_parsed,
Attempting::NumberLiteral,
state.clone(),
))
};
let is_potentially_non_base10 = || {
(bytes_parsed == 1 && first_ch == '0')
|| (bytes_parsed == 2 && first_ch == '-' && prev_byte == b'0')
};
match next_byte as char {
'.' => {
if typ == Float {
// You only get one decimal point!
return err_unexpected();
} else {
typ = Float;
}
}
'x' => {
if is_potentially_non_base10() {
typ = Hex;
} else {
return err_unexpected();
}
}
'b' if typ == Num => {
// We have to check for typ == Num because otherwise we get a false
// positive here when parsing a hex literal that happens to have
// a 'b' in it, e.g. 0xbbbb
if is_potentially_non_base10() {
typ = Binary;
} else {
return err_unexpected();
}
}
'o' => {
if is_potentially_non_base10() {
typ = Octal;
} else {
return err_unexpected();
}
}
'_' => {
// Underscores are ignored.
}
next_ch => {
if next_ch.is_ascii_digit() {
has_parsed_digits = true;
} else {
if !has_parsed_digits {
// No digits! We likely parsed a minus sign
// that's actually a unary negation operator.
return err_unexpected();
}
// ASCII alphabetic chars (like 'a' and 'f') are
// allowed in Hex int literals. We verify them in
// canonicalization, so if there's a problem, we can
// give a more helpful error (e.g. "the character 'f'
// is not allowed in Octal literals" or
// "the character 'g' is outside the range of valid
// Hex literals") while still allowing the formatter
// to format them normally.
if !next_ch.is_ascii_alphabetic() {
// We hit an invalid number literal character; we're done!
break;
}
}
}
}
// Since we only consume characters in the ASCII range for number literals,
// this will always be exactly 1. There's no need to call next_ch.utf8_len().
bytes_parsed += 1;
prev_byte = next_byte;
}
// At this point we have a number, and will definitely succeed.
// If the number is malformed (outside the supported range),
// we'll succeed with an appropriate Expr which records that.
match typ {
Num => Ok((
Progress::from_consumed(bytes_parsed),
// SAFETY: it's safe to use from_utf8_unchecked here, because we've
// already validated that this range contains only ASCII digits
Expr::Num(unsafe { from_utf8_unchecked(&state.bytes[0..bytes_parsed]) }),
state.advance_without_indenting(arena, bytes_parsed)?,
)),
Float => Ok((
Progress::from_consumed(bytes_parsed),
// SAFETY: it's safe to use from_utf8_unchecked here, because we've
// already validated that this range contains only ASCII digits
Expr::Float(unsafe { from_utf8_unchecked(&state.bytes[0..bytes_parsed]) }),
state.advance_without_indenting(arena, bytes_parsed)?,
)),
// For these we trim off the 0x/0o/0b part
Hex => from_base(Base::Hex, first_ch, bytes_parsed, arena, state),
Octal => from_base(Base::Octal, first_ch, bytes_parsed, arena, state),
Binary => from_base(Base::Binary, first_ch, bytes_parsed, arena, state),
) -> ParseResult<'a, NumLiteral<'a>, Number> {
match bytes.get(0..2) {
Some(b"0b") => chomp_number_base(Base::Binary, is_negated, &bytes[2..], state),
Some(b"0o") => chomp_number_base(Base::Octal, is_negated, &bytes[2..], state),
Some(b"0x") => chomp_number_base(Base::Hex, is_negated, &bytes[2..], state),
_ => chomp_number_dec(is_negated, bytes, state),
}
}
#[derive(Debug, PartialEq, Eq)]
enum LiteralType {
Num,
Float,
Hex,
Octal,
Binary,
}
fn from_base<'a>(
fn chomp_number_base<'a>(
base: Base,
first_ch: char,
bytes_parsed: usize,
arena: &'a Bump,
is_negative: bool,
bytes: &'a [u8],
state: State<'a>,
) -> ParseResult<'a, Expr<'a>, SyntaxError<'a>> {
let is_negative = first_ch == '-';
let bytes = if is_negative {
&state.bytes[3..bytes_parsed]
} else {
&state.bytes[2..bytes_parsed]
};
) -> ParseResult<'a, NumLiteral<'a>, Number> {
let (_is_float, chomped) = chomp_number(bytes);
match parse_utf8(bytes) {
Ok(string) => Ok((
Progress::from_consumed(bytes_parsed),
Expr::NonBase10Int {
is_negative,
string,
base,
},
state.advance_without_indenting(arena, bytes_parsed)?,
)),
Err(reason) => state.fail(arena, Progress::from_consumed(bytes_parsed), reason),
}
let string = unsafe { std::str::from_utf8_unchecked(&bytes[..chomped]) };
let new = state.advance_without_indenting_ee(chomped + 2 + is_negative as usize, |_, _| {
Number::LineTooLong
})?;
Ok((
Progress::MadeProgress,
NumLiteral::NonBase10Int {
is_negative,
string,
base,
},
new,
))
}
fn chomp_number_dec<'a>(
is_negative: bool,
bytes: &'a [u8],
state: State<'a>,
) -> ParseResult<'a, NumLiteral<'a>, Number> {
let (is_float, chomped) = chomp_number(bytes);
if is_negative && chomped == 0 {
// we're probably actually looking at unary negation here
return Err((Progress::NoProgress, Number::End, state));
}
if !bytes.get(0).copied().unwrap_or_default().is_ascii_digit() {
// we're probably actually looking at unary negation here
return Err((Progress::NoProgress, Number::End, state));
}
let string =
unsafe { std::str::from_utf8_unchecked(&state.bytes[0..chomped + is_negative as usize]) };
let new = state
.advance_without_indenting_ee(chomped + is_negative as usize, |_, _| Number::LineTooLong)?;
Ok((
Progress::MadeProgress,
if is_float {
NumLiteral::Float(string)
} else {
NumLiteral::Num(string)
},
new,
))
}
fn chomp_number(mut bytes: &[u8]) -> (bool, usize) {
let start_bytes_len = bytes.len();
let mut is_float = false;
while let Some(byte) = bytes.get(0) {
match byte {
b'.' => {
// skip, fix multiple `.`s in canonicalization
is_float = true;
bytes = &bytes[1..];
}
b'e' => {
// maybe scientific notation?
match bytes.get(1) {
Some(b'-') => {
is_float = true;
bytes = &bytes[2..];
}
Some(c) if (*c as char).is_ascii_digit() => {
is_float = true;
bytes = &bytes[2..];
}
_ => {
bytes = &bytes[1..];
}
}
}
b'_' => {
// skip
bytes = &bytes[1..];
}
_ if byte.is_ascii_digit() || byte.is_ascii_alphabetic() => {
// valid digits (alphabetic in hex digits, and the `e` in `12e26` scientific notation
bytes = &bytes[1..];
}
_ => {
// not a valid digit; we're done
return (is_float, start_bytes_len - bytes.len());
}
}
}
// if the above loop exits, we must be dealing with an empty slice
// therefore we parsed all of the bytes in the input
(is_float, start_bytes_len)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,16 @@
use crate::ast::Pattern;
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::ident::{lowercase_ident, parse_ident_help, Ident};
use crate::parser::Progress::{self, *};
use crate::parser::{
backtrackable, optional, specialize, specialize_ref, word1, EPattern, PInParens, PRecord,
ParseResult, Parser, State,
};
use bumpalo::collections::string::String;
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_region::all::{Located, Region};
/// Different patterns are supported in different circumstances.
/// For example, when branches can pattern match on number literals, but
/// assignments and function args can't. Underscore is supported in function
@ -9,3 +22,419 @@ pub enum PatternType {
FunctionArg,
WhenBranch,
}
pub fn loc_closure_param<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, EPattern<'a>> {
move |arena, state| parse_closure_param(arena, state, min_indent)
}
fn parse_closure_param<'a>(
arena: &'a Bump,
state: State<'a>,
min_indent: u16,
) -> ParseResult<'a, Located<Pattern<'a>>, EPattern<'a>> {
one_of!(
// An ident is the most common param, e.g. \foo -> ...
loc_ident_pattern_help(min_indent, true),
// Underscore is also common, e.g. \_ -> ...
loc!(underscore_pattern_help()),
// You can destructure records in params, e.g. \{ x, y } -> ...
loc!(specialize(
EPattern::Record,
crate::pattern::record_pattern_help(min_indent)
)),
// If you wrap it in parens, you can match any arbitrary pattern at all.
// e.g. \User.UserId userId -> ...
specialize(EPattern::PInParens, loc_pattern_in_parens_help(min_indent))
)
.parse(arena, state)
}
pub fn loc_pattern_help<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, EPattern<'a>> {
one_of!(
specialize(EPattern::PInParens, loc_pattern_in_parens_help(min_indent)),
loc!(underscore_pattern_help()),
loc_ident_pattern_help(min_indent, true),
loc!(specialize(
EPattern::Record,
crate::pattern::record_pattern_help(min_indent)
)),
loc!(string_pattern_help()),
loc!(number_pattern_help())
)
}
fn loc_tag_pattern_args_help<'a>(
min_indent: u16,
) -> impl Parser<'a, Vec<'a, Located<Pattern<'a>>>, EPattern<'a>> {
zero_or_more!(loc_tag_pattern_arg(min_indent))
}
fn loc_tag_pattern_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>, EPattern<'a>> {
// Don't parse operators, because they have a higher precedence than function application.
// If we encounter one, we're done parsing function args!
move |arena, state| {
let (_, spaces, state) =
backtrackable(space0_e(min_indent, EPattern::Space, EPattern::IndentStart))
.parse(arena, state)?;
let (_, loc_pat, state) = loc_parse_tag_pattern_arg(min_indent, arena, state)?;
let Located { region, value } = loc_pat;
Ok((
MadeProgress,
if spaces.is_empty() {
Located::at(region, value)
} else {
Located::at(region, Pattern::SpaceBefore(arena.alloc(value), spaces))
},
state,
))
}
}
fn loc_parse_tag_pattern_arg<'a>(
min_indent: u16,
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Located<Pattern<'a>>, EPattern<'a>> {
one_of!(
specialize(EPattern::PInParens, loc_pattern_in_parens_help(min_indent)),
loc!(underscore_pattern_help()),
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
loc_ident_pattern_help(min_indent, false),
loc!(specialize(
EPattern::Record,
crate::pattern::record_pattern_help(min_indent)
)),
loc!(string_pattern_help()),
loc!(number_pattern_help())
)
.parse(arena, state)
}
fn loc_pattern_in_parens_help<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, PInParens<'a>> {
between!(
word1(b'(', PInParens::Open),
space0_around_ee(
move |arena, state| specialize_ref(PInParens::Pattern, loc_pattern_help(min_indent))
.parse(arena, state),
min_indent,
PInParens::Space,
PInParens::IndentOpen,
PInParens::IndentEnd,
),
word1(b')', PInParens::End)
)
}
fn number_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
specialize(
EPattern::NumLiteral,
map!(crate::number_literal::number_literal(), |literal| {
use crate::number_literal::NumLiteral::*;
match literal {
Num(s) => Pattern::NumLiteral(s),
Float(s) => Pattern::FloatLiteral(s),
NonBase10Int {
string,
base,
is_negative,
} => Pattern::NonBase10Literal {
string,
base,
is_negative,
},
}
}),
)
}
fn string_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
specialize(
|_, r, c| EPattern::Start(r, c),
map!(crate::string_literal::parse(), Pattern::StrLiteral),
)
}
fn loc_ident_pattern_help<'a>(
min_indent: u16,
can_have_arguments: bool,
) -> impl Parser<'a, Located<Pattern<'a>>, EPattern<'a>> {
move |arena: &'a Bump, state: State<'a>| {
let original_state = state;
let (_, loc_ident, state) =
specialize(|_, r, c| EPattern::Start(r, c), loc!(parse_ident_help))
.parse(arena, state)?;
match loc_ident.value {
Ident::GlobalTag(tag) => {
let loc_tag = Located {
region: loc_ident.region,
value: Pattern::GlobalTag(tag),
};
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
if can_have_arguments {
let (_, loc_args, state) =
loc_tag_pattern_args_help(min_indent).parse(arena, state)?;
if loc_args.is_empty() {
Ok((MadeProgress, loc_tag, state))
} else {
let region = Region::across_all(
std::iter::once(&loc_ident.region)
.chain(loc_args.iter().map(|loc_arg| &loc_arg.region)),
);
let value =
Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice());
Ok((MadeProgress, Located { region, value }, state))
}
} else {
Ok((MadeProgress, loc_tag, state))
}
}
Ident::PrivateTag(tag) => {
let loc_tag = Located {
region: loc_ident.region,
value: Pattern::PrivateTag(tag),
};
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
if can_have_arguments {
let (_, loc_args, state) =
loc_tag_pattern_args_help(min_indent).parse(arena, state)?;
if loc_args.is_empty() {
Ok((MadeProgress, loc_tag, state))
} else {
let region = Region::across_all(
std::iter::once(&loc_ident.region)
.chain(loc_args.iter().map(|loc_arg| &loc_arg.region)),
);
let value =
Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice());
Ok((MadeProgress, Located { region, value }, state))
}
} else {
Ok((MadeProgress, loc_tag, state))
}
}
Ident::Access { module_name, parts } => {
// Plain identifiers (e.g. `foo`) are allowed in patterns, but
// more complex ones (e.g. `Foo.bar` or `foo.bar.baz`) are not.
if crate::keyword::KEYWORDS.contains(&parts[0]) {
Err((
NoProgress,
EPattern::End(original_state.line, original_state.column),
original_state,
))
} else if module_name.is_empty() && parts.len() == 1 {
Ok((
MadeProgress,
Located {
region: loc_ident.region,
value: Pattern::Identifier(parts[0]),
},
state,
))
} else {
let malformed_str = if module_name.is_empty() {
parts.join(".")
} else {
format!("{}.{}", module_name, parts.join("."))
};
Ok((
MadeProgress,
Located {
region: loc_ident.region,
value: Pattern::Malformed(
String::from_str_in(&malformed_str, &arena).into_bump_str(),
),
},
state,
))
}
}
Ident::AccessorFunction(string) => Ok((
MadeProgress,
Located {
region: loc_ident.region,
value: Pattern::Malformed(string),
},
state,
)),
Ident::Malformed(malformed, problem) => {
debug_assert!(!malformed.is_empty());
Ok((
MadeProgress,
Located {
region: loc_ident.region,
value: Pattern::MalformedIdent(malformed, problem),
},
state,
))
}
}
}
}
fn underscore_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
move |arena: &'a Bump, state: State<'a>| {
let (_, _, next_state) = word1(b'_', EPattern::Underscore).parse(arena, state)?;
let (_, output, final_state) =
optional(lowercase_ident_pattern).parse(arena, next_state)?;
match output {
Some(name) => Ok((MadeProgress, Pattern::Underscore(name), final_state)),
None => Ok((MadeProgress, Pattern::Underscore(&""), final_state)),
}
}
}
fn lowercase_ident_pattern<'a>(
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, &'a str, EPattern<'a>> {
let row = state.line;
let col = state.column;
specialize(move |_, _, _| EPattern::End(row, col), lowercase_ident()).parse(arena, state)
}
#[inline(always)]
fn record_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>, PRecord<'a>> {
move |arena, state| {
let (_, (fields, final_comments), state) = collection_trailing_sep_e!(
// word1_check_indent!(b'{', PRecord::Open, min_indent, PRecord::IndentOpen),
word1(b'{', PRecord::Open),
record_pattern_field(min_indent),
word1(b',', PRecord::End),
// word1_check_indent!(b'}', PRecord::End, min_indent, PRecord::IndentEnd),
word1(b'}', PRecord::End),
min_indent,
PRecord::Open,
PRecord::Space,
PRecord::IndentEnd
)
.parse(arena, state)?;
// TODO
let _unused = final_comments;
let result = Pattern::RecordDestructure(fields.into_bump_slice());
Ok((MadeProgress, result, state))
}
}
fn record_pattern_field<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>, PRecord<'a>> {
use crate::parser::Either::*;
move |arena, state: State<'a>| {
// You must have a field name, e.g. "email"
// using the initial row/col is important for error reporting
let row = state.line;
let col = state.column;
let (progress, loc_label, state) = loc!(specialize(
move |_, _, _| PRecord::Field(row, col),
lowercase_ident()
))
.parse(arena, state)?;
debug_assert_eq!(progress, MadeProgress);
let (_, spaces, state) =
space0_e(min_indent, PRecord::Space, PRecord::IndentEnd).parse(arena, state)?;
// Having a value is optional; both `{ email }` and `{ email: blah }` work.
// (This is true in both literals and types.)
let (_, opt_loc_val, state) = optional(either!(
word1(b':', PRecord::Colon),
word1(b'?', PRecord::Optional)
))
.parse(arena, state)?;
match opt_loc_val {
Some(First(_)) => {
let val_parser = specialize_ref(PRecord::Pattern, loc_pattern_help(min_indent));
let (_, loc_val, state) =
space0_before_e(val_parser, min_indent, PRecord::Space, PRecord::IndentColon)
.parse(arena, state)?;
let Located {
value: label,
region,
} = loc_label;
let region = Region::span_across(&region, &loc_val.region);
Ok((
MadeProgress,
Located::at(
region,
Pattern::RequiredField(
label,
// TODO spaces are dropped here
// arena.alloc(arena.alloc(value).with_spaces_before(spaces, region)),
arena.alloc(loc_val),
),
),
state,
))
}
Some(Second(_)) => {
let val_parser =
specialize_ref(PRecord::Expr, loc!(crate::expr::expr_help(min_indent)));
let (_, loc_val, state) =
space0_before_e(val_parser, min_indent, PRecord::Space, PRecord::IndentColon)
.parse(arena, state)?;
let Located {
value: label,
region,
} = loc_label;
let region = Region::span_across(&region, &loc_val.region);
Ok((
MadeProgress,
Located::at(
region,
Pattern::OptionalField(
label,
// TODO spaces are dropped
// arena.alloc(arena.alloc(value).with_spaces_before(spaces, region)),
arena.alloc(loc_val),
),
),
state,
))
}
// If no value was provided, record it as a Var.
// Canonicalize will know what to do with a Var later.
None => {
let Located { value, region } = loc_label;
let value = if !spaces.is_empty() {
Pattern::SpaceAfter(arena.alloc(Pattern::Identifier(value)), spaces)
} else {
Pattern::Identifier(value)
};
Ok((MadeProgress, Located::at(region, value), state))
}
}
}
}

View File

@ -1,33 +1,71 @@
use crate::ast::{Attempting, EscapedChar, StrLiteral, StrSegment};
use crate::ast::{EscapedChar, StrLiteral, StrSegment};
use crate::expr;
use crate::parser::Progress::*;
use crate::parser::{
allocated, ascii_char, ascii_hex_digits, loc, parse_utf8, unexpected, unexpected_eof,
ParseResult, Parser, State, SyntaxError,
};
use crate::parser::{allocated, loc, specialize_ref, word1, BadInputError, EString, Parser, State};
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, SyntaxError<'a>> {
use StrLiteral::*;
/// One or more ASCII hex digits. (Useful when parsing unicode escape codes,
/// which must consist entirely of ASCII hex digits.)
fn ascii_hex_digits<'a>() -> impl Parser<'a, &'a str, EString<'a>> {
move |arena, state: State<'a>| {
let mut buf = bumpalo::collections::String::new_in(arena);
move |arena: &'a Bump, mut state: State<'a>| {
let mut bytes = state.bytes.iter();
// String literals must start with a quote.
// If this doesn't, it must not be a string literal!
match bytes.next() {
Some(&byte) => {
if byte != b'"' {
return Err(unexpected(arena, 0, Attempting::StrLiteral, state));
}
}
None => {
return Err(unexpected_eof(arena, state, 0));
for &byte in state.bytes.iter() {
if (byte as char).is_ascii_hexdigit() {
buf.push(byte as char);
} else if buf.is_empty() {
// We didn't find any hex digits!
return Err((
NoProgress,
EString::CodePointEnd(state.line, state.column),
state,
));
} else {
let state = state.advance_without_indenting_ee(buf.len(), |r, c| {
EString::Space(BadInputError::LineTooLong, r, c)
})?;
return Ok((MadeProgress, buf.into_bump_str(), state));
}
}
// Advance past the opening quotation mark.
state = state.advance_without_indenting(arena, 1)?;
Err((
NoProgress,
EString::CodePointEnd(state.line, state.column),
state,
))
}
}
macro_rules! advance_state {
($state:expr, $n:expr) => {
$state.advance_without_indenting_ee($n, |r, c| {
EString::Space(BadInputError::LineTooLong, r, c)
})
};
}
pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> {
use StrLiteral::*;
move |arena: &'a Bump, mut state: State<'a>| {
let is_multiline;
let mut bytes;
if state.bytes.starts_with(b"\"\"\"") {
// we will be parsing a multi-string
is_multiline = true;
bytes = state.bytes[3..].iter();
state = advance_state!(state, 3)?;
} else if state.bytes.starts_with(b"\"") {
// we will be parsing a single-string
is_multiline = false;
bytes = state.bytes[1..].iter();
state = advance_state!(state, 1)?;
} else {
return Err((NoProgress, EString::Open(state.line, state.column), state));
}
// At the parsing stage we keep the entire raw string, because the formatter
// needs the raw string. (For example, so it can "remember" whether you
@ -44,7 +82,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, SyntaxError<'a>> {
segments.push(StrSegment::EscapedChar($ch));
// Advance past the segment we just added
state = state.advance_without_indenting(arena, segment_parsed_bytes)?;
state = advance_state!(state, segment_parsed_bytes)?;
// Reset the segment
segment_parsed_bytes = 0;
@ -61,14 +99,18 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, SyntaxError<'a>> {
// to exclude that char we just parsed.
let string_bytes = &state.bytes[0..(segment_parsed_bytes - 1)];
match parse_utf8(string_bytes) {
match std::str::from_utf8(string_bytes) {
Ok(string) => {
state = state.advance_without_indenting(arena, string.len())?;
state = advance_state!(state, string.len())?;
segments.push($transform(string));
}
Err(reason) => {
return state.fail(arena, MadeProgress, reason);
Err(_) => {
return Err((
MadeProgress,
EString::Space(BadInputError::BadUtf8, state.line, state.column),
state,
));
}
}
}
@ -91,62 +133,79 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, SyntaxError<'a>> {
match byte {
b'"' => {
// This is the end of the string!
if segment_parsed_bytes == 1 && segments.is_empty() {
match bytes.next() {
Some(b'"') => {
// If the very first three chars were all `"`,
// then this literal begins with `"""`
// and is a block string.
return parse_block_string(arena, state, &mut bytes);
}
_ => {
// Advance 1 for the close quote
return Ok((
MadeProgress,
PlainLine(""),
state.advance_without_indenting(arena, 1)?,
));
}
}
} else {
end_segment!(StrSegment::Plaintext);
let expr = if segments.len() == 1 {
// We had exactly one segment, so this is a candidate
// to be StrLiteral::Plaintext
match segments.pop().unwrap() {
StrSegment::Plaintext(string) => StrLiteral::PlainLine(string),
other => {
let vec = bumpalo::vec![in arena; other];
StrLiteral::Line(vec.into_bump_slice())
}
// special case of the empty string
if is_multiline {
if bytes.as_slice().starts_with(b"\"\"") {
return Ok((MadeProgress, Block(&[]), advance_state!(state, 3)?));
} else {
// this quote is in a block string
continue;
}
} else {
Line(segments.into_bump_slice())
};
// This is the end of the string!
// Advance 1 for the close quote
return Ok((MadeProgress, PlainLine(""), advance_state!(state, 1)?));
}
} else {
// the string is non-empty, which means we need to convert any previous segments
// and the current segment into a string literal
if is_multiline {
if bytes.as_slice().starts_with(b"\"\"") {
end_segment!(StrSegment::Plaintext);
// Advance the state 1 to account for the closing `"`
return Ok((
MadeProgress,
expr,
state.advance_without_indenting(arena, 1)?,
));
let expr = if segments.len() == 1 {
// We had exactly one segment, so this is a candidate
// to be StrLiteral::Plaintext
match segments.pop().unwrap() {
StrSegment::Plaintext(string) => {
StrLiteral::PlainLine(string)
}
other => StrLiteral::Line(arena.alloc([other])),
}
} else {
Block(arena.alloc([segments.into_bump_slice()]))
};
return Ok((MadeProgress, expr, advance_state!(state, 3)?));
} else {
// this quote is in a block string
continue;
}
} else {
end_segment!(StrSegment::Plaintext);
let expr = if segments.len() == 1 {
// We had exactly one segment, so this is a candidate
// to be StrLiteral::Plaintext
match segments.pop().unwrap() {
StrSegment::Plaintext(string) => StrLiteral::PlainLine(string),
other => StrLiteral::Line(arena.alloc([other])),
}
} else {
Line(segments.into_bump_slice())
};
// Advance the state 1 to account for the closing `"`
return Ok((MadeProgress, expr, advance_state!(state, 1)?));
}
};
}
b'\n' => {
// This is a single-line string, which cannot have newlines!
// Treat this as an unclosed string literal, and consume
// all remaining chars. This will mask all other errors, but
// it should make it easiest to debug; the file will be a giant
// error starting from where the open quote appeared.
return Err(unexpected(
arena,
state.bytes.len() - 1,
Attempting::StrLiteral,
state,
));
if is_multiline {
continue;
} else {
// This is a single-line string, which cannot have newlines!
// Treat this as an unclosed string literal, and consume
// all remaining chars. This will mask all other errors, but
// it should make it easiest to debug; the file will be a giant
// error starting from where the open quote appeared.
return Err((
MadeProgress,
EString::EndlessSingle(state.line, state.column),
state,
));
}
}
b'\\' => {
// We're about to begin an escaped segment of some sort!
@ -163,7 +222,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, SyntaxError<'a>> {
match bytes.next() {
Some(b'(') => {
// Advance past the `\(` before using the expr parser
state = state.advance_without_indenting(arena, 2)?;
state = advance_state!(state, 2)?;
let original_byte_count = state.bytes.len();
@ -171,9 +230,11 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, SyntaxError<'a>> {
// Parse an arbitrary expression, then give a
// canonicalization error if that expression variant
// is not allowed inside a string interpolation.
let (_progress, loc_expr, new_state) =
skip_second!(loc(allocated(expr::expr(0))), ascii_char(b')'))
.parse(arena, state)?;
let (_progress, loc_expr, new_state) = skip_second!(
specialize_ref(EString::Format, loc(allocated(expr::expr_help(0)))),
word1(b')', EString::FormatEnd)
)
.parse(arena, state)?;
// Advance the iterator past the expr we just parsed.
for _ in 0..(original_byte_count - new_state.bytes.len()) {
@ -188,7 +249,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, SyntaxError<'a>> {
}
Some(b'u') => {
// Advance past the `\u` before using the expr parser
state = state.advance_without_indenting(arena, 2)?;
state = advance_state!(state, 2)?;
let original_byte_count = state.bytes.len();
@ -196,9 +257,9 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, SyntaxError<'a>> {
// give a canonicalization error if the digits form
// an invalid unicode code point.
let (_progress, loc_digits, new_state) = between!(
ascii_char(b'('),
word1(b'(', EString::CodePointOpen),
loc(ascii_hex_digits()),
ascii_char(b')')
word1(b')', EString::CodePointEnd)
)
.parse(arena, state)?;
@ -232,10 +293,9 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, SyntaxError<'a>> {
// Invalid escape! A backslash must be followed
// by either an open paren or else one of the
// escapable characters (\n, \t, \", \\, etc)
return Err(unexpected(
arena,
state.bytes.len() - 1,
Attempting::StrLiteral,
return Err((
MadeProgress,
EString::UnknownEscape(state.line, state.column),
state,
));
}
@ -248,85 +308,14 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, SyntaxError<'a>> {
}
// We ran out of characters before finding a closed quote
Err(unexpected_eof(arena, state.clone(), state.bytes.len()))
Err((
MadeProgress,
if is_multiline {
EString::EndlessMulti(state.line, state.column)
} else {
EString::EndlessSingle(state.line, state.column)
},
state,
))
}
}
fn parse_block_string<'a, I>(
arena: &'a Bump,
state: State<'a>,
bytes: &mut I,
) -> ParseResult<'a, StrLiteral<'a>, SyntaxError<'a>>
where
I: Iterator<Item = &'a u8>,
{
// So far we have consumed the `"""` and that's it.
let mut parsed_chars = 3;
let mut prev_byte = b'"';
let mut quotes_seen = 0;
// start at 3 to omit the opening `"`.
let mut line_start = 3;
let mut lines: Vec<'a, &'a str> = Vec::new_in(arena);
for byte in bytes {
parsed_chars += 1;
// Potentially end the string (unless this is an escaped `"`!)
match byte {
b'"' if prev_byte != b'\\' => {
if quotes_seen == 2 {
// three consecutive qoutes, end string
// Subtract 3 from parsed_chars so we omit the closing `"`.
let line_bytes = &state.bytes[line_start..(parsed_chars - 3)];
return match parse_utf8(line_bytes) {
Ok(line) => {
// state = state.advance_without_indenting(parsed_chars)?;
// lines.push(line);
// Ok((StrLiteral::Block(lines.into_bump_slice()), state))
Err((
MadeProgress,
SyntaxError::NotYetImplemented(format!(
"TODO parse this line in a block string: {:?}",
line
)),
state,
))
}
Err(reason) => state.fail(arena, MadeProgress, reason),
};
}
quotes_seen += 1;
}
b'\n' => {
// note this includes the newline
let line_bytes = &state.bytes[line_start..parsed_chars];
match parse_utf8(line_bytes) {
Ok(line) => {
lines.push(line);
quotes_seen = 0;
line_start = parsed_chars;
}
Err(reason) => {
return state.fail(arena, MadeProgress, reason);
}
}
}
_ => {
quotes_seen = 0;
}
}
prev_byte = *byte;
}
// We ran out of characters before finding 3 closing quotes
Err(unexpected_eof(arena, state, parsed_chars))
}

View File

@ -1,13 +1,10 @@
use crate::ast::{self, Attempting};
use crate::blankspace::space0_before;
use crate::expr::expr;
use crate::module::{header, module_defs};
use crate::parser::{loc, Parser, State, SyntaxError};
use crate::ast;
use crate::module::module_defs;
use crate::parser::{Parser, State, SyntaxError};
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_region::all::Located;
#[allow(dead_code)]
pub fn parse_expr_with<'a>(
arena: &'a Bump,
input: &'a str,
@ -15,24 +12,12 @@ pub fn parse_expr_with<'a>(
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
}
pub fn parse_header_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<ast::Module<'a>, SyntaxError<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let answer = header().parse(arena, state);
answer
.map(|(_, loc_expr, _)| loc_expr)
.map_err(|(_, fail, _)| fail)
}
#[allow(dead_code)]
pub fn parse_defs_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<Vec<'a, Located<ast::Def<'a>>>, SyntaxError<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let state = State::new(input.trim().as_bytes());
let answer = module_defs().parse(arena, state);
answer
.map(|(_, loc_expr, _)| loc_expr)
@ -44,11 +29,10 @@ pub fn parse_loc_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<Located<ast::Expr<'a>>, SyntaxError<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc(expr(0)), 0);
let answer = parser.parse(&arena, state);
let state = State::new(input.trim().as_bytes());
answer
.map(|(_, loc_expr, _)| loc_expr)
.map_err(|(_, fail, _)| fail)
match crate::expr::test_parse_expr(0, arena, state) {
Ok(loc_expr) => Ok(loc_expr),
Err(fail) => Err(SyntaxError::Expr(fail)),
}
}

View File

@ -1,22 +1,18 @@
use crate::ast::{AssignedField, Tag, TypeAnnotation};
use crate::blankspace::{space0_around_e, space0_before_e, space0_e};
use crate::ident::join_module_parts;
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::keyword;
use crate::parser::{
allocated, backtrackable, not_e, optional, peek_utf8_char_e, specialize, specialize_ref, word1,
word2, BadInputError, ParseResult, Parser,
allocated, backtrackable, optional, specialize, specialize_ref, word1, word2, ParseResult,
Parser,
Progress::{self, *},
State, SyntaxError, TApply, TInParens, TRecord, TTagUnion, TVariable, Type,
State, TApply, TInParens, TRecord, TTagUnion, Type,
};
use bumpalo::collections::string::String;
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use roc_region::all::{Located, Region};
pub fn located<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<TypeAnnotation<'a>>, SyntaxError<'a>> {
specialize(|x, _, _| SyntaxError::Type(x), expression(min_indent))
pub fn located_help<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> {
expression(min_indent)
}
#[inline(always)]
@ -58,7 +54,7 @@ fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Typ
loc!(specialize(Type::TRecord, record_type(min_indent))),
loc!(specialize(Type::TTagUnion, tag_union_type(min_indent))),
loc!(applied_type(min_indent)),
loc!(specialize(Type::TVariable, parse_type_variable))
loc!(parse_type_variable)
),
// Inline alias notation, e.g. [ Nil, Cons a (List a) ] as List a
one_of![
@ -66,7 +62,7 @@ fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Typ
and!(
skip_second!(
backtrackable(space0_e(min_indent, Type::TSpace, Type::TIndentEnd)),
crate::parser::keyword_e(keyword::AS, Type::TEnd(0, 0))
crate::parser::keyword_e(keyword::AS, Type::TEnd)
),
space0_before_e(
term(min_indent),
@ -113,21 +109,13 @@ fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotatio
map_with_arena!(
and!(
backtrackable(space0_e(min_indent, Type::TSpace, Type::TIndentStart)),
skip_first!(
// Once we hit an "as", stop parsing args
// and roll back parsing of preceding spaces
not_e(
crate::parser::keyword(keyword::AS, min_indent),
Type::TStart
),
one_of!(
loc_wildcard(),
specialize(Type::TInParens, loc_type_in_parens(min_indent)),
loc!(specialize(Type::TRecord, record_type(min_indent))),
loc!(specialize(Type::TTagUnion, tag_union_type(min_indent))),
loc!(specialize(Type::TApply, parse_concrete_type)),
loc!(specialize(Type::TVariable, parse_type_variable))
)
one_of!(
loc_wildcard(),
specialize(Type::TInParens, loc_type_in_parens(min_indent)),
loc!(specialize(Type::TRecord, record_type(min_indent))),
loc!(specialize(Type::TTagUnion, tag_union_type(min_indent))),
loc!(specialize(Type::TApply, parse_concrete_type)),
loc!(parse_type_variable)
)
),
|arena: &'a Bump, (spaces, argument): (&'a [_], Located<TypeAnnotation<'a>>)| {
@ -144,14 +132,14 @@ fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotatio
fn loc_type_in_parens<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<TypeAnnotation<'a>>, TInParens<'a>> {
// TODO what if the middle parser returns EOF?
between!(
word1(b'(', TInParens::Open),
space0_around_e(
space0_around_ee(
move |arena, state| specialize_ref(TInParens::Type, expression(min_indent))
.parse(arena, state),
min_indent,
TInParens::Space,
TInParens::IndentOpen,
TInParens::IndentEnd,
),
word1(b')', TInParens::End)
@ -188,102 +176,9 @@ where
F: Fn(Row, Col) -> E,
E: 'a,
{
use encode_unicode::CharExt;
move |arena, mut state: State<'a>| {
let mut buf;
match char::from_utf8_slice_start(state.bytes) {
Ok((first_letter, bytes_parsed)) => match first_letter {
'@' => {
debug_assert_eq!(bytes_parsed, 1);
// parsing a private tag name
match char::from_utf8_slice_start(&state.bytes[1..]) {
Ok((second_letter, bytes_parsed_2)) if second_letter.is_uppercase() => {
let total_parsed = bytes_parsed + bytes_parsed_2;
buf = String::with_capacity_in(total_parsed, arena);
buf.push('@');
buf.push(second_letter);
state = state
.advance_without_indenting(arena, total_parsed)
.map_err(|(progress, _, state)| {
(progress, to_problem(state.line, state.column), state)
})?;
}
_ => {
// important for error messages
state = state
.advance_without_indenting(arena, bytes_parsed)
.map_err(|(progress, _, state)| {
(progress, to_problem(state.line, state.column), state)
})?;
let row = state.line;
let col = state.column;
return state.fail(arena, MadeProgress, to_problem(row, col));
}
}
}
_ if first_letter.is_uppercase() => {
buf = String::with_capacity_in(1, arena);
buf.push(first_letter);
state = state
.advance_without_indenting(arena, bytes_parsed)
.map_err(|(progress, _, state)| {
(progress, to_problem(state.line, state.column), state)
})?;
}
_ => {
let row = state.line;
let col = state.column;
return state.fail(arena, NoProgress, to_problem(row, col));
}
},
Err(_) => {
let row = state.line;
let col = state.column;
return state.fail(arena, NoProgress, to_problem(row, col));
}
};
while !state.bytes.is_empty() {
match char::from_utf8_slice_start(state.bytes) {
Ok((ch, bytes_parsed)) => {
// After the first character, only these are allowed:
//
// * Unicode alphabetic chars - you might include `鹏` if that's clear to your readers
// * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric()
// * A ':' indicating the end of the field
if ch.is_alphabetic() || ch.is_ascii_digit() {
buf.push(ch);
state = state
.advance_without_indenting(arena, bytes_parsed)
.map_err(|(progress, _, state)| {
(progress, to_problem(state.line, state.column), state)
})?;
} else {
// This is the end of the field. We're done!
break;
}
}
Err(_) => {
let row = state.line;
let col = state.column;
return state.fail(arena, MadeProgress, to_problem(row, col));
}
};
}
Ok((MadeProgress, buf.into_bump_str(), state))
move |arena, state: State<'a>| match crate::ident::tag_name().parse(arena, state) {
Ok(good) => Ok(good),
Err((progress, _, state)) => Err((progress, to_problem(state.line, state.column), state)),
}
}
@ -437,11 +332,12 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
let (p2, rest, state) = zero_or_more!(skip_first!(
word1(b',', Type::TFunctionArgument),
one_of![
space0_around_e(
space0_around_ee(
term(min_indent),
min_indent,
Type::TSpace,
Type::TIndentStart
Type::TIndentStart,
Type::TIndentEnd
),
|_, state: State<'a>| Err((
NoProgress,
@ -512,156 +408,52 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
fn parse_concrete_type<'a>(
arena: &'a Bump,
mut state: State<'a>,
state: State<'a>,
) -> ParseResult<'a, TypeAnnotation<'a>, TApply> {
let mut part_buf = String::new_in(arena); // The current "part" (parts are dot-separated.)
let mut parts: Vec<&'a str> = Vec::new_in(arena);
let initial_bytes = state.bytes;
// Qualified types must start with a capitalized letter.
match peek_utf8_char_e(&state, TApply::StartNotUppercase, TApply::Space) {
Ok((first_letter, bytes_parsed)) => {
if first_letter.is_alphabetic() && first_letter.is_uppercase() {
part_buf.push(first_letter);
} else {
let problem = TApply::StartNotUppercase(state.line, state.column + 1);
return Err((NoProgress, problem, state));
}
match crate::ident::concrete_type().parse(arena, state) {
Ok((_, (module_name, type_name), state)) => {
let answer = TypeAnnotation::Apply(module_name, type_name, &[]);
state = state.advance_without_indenting_e(arena, bytes_parsed, TApply::Space)?;
Ok((MadeProgress, answer, state))
}
Err(reason) => return Err((NoProgress, reason, state)),
}
Err((NoProgress, _, state)) => {
Err((NoProgress, TApply::End(state.line, state.column), state))
}
Err((MadeProgress, _, mut state)) => {
// we made some progress, but ultimately failed.
// that means a malformed type name
let chomped = crate::ident::chomp_malformed(state.bytes);
let delta = initial_bytes.len() - state.bytes.len();
let parsed_str =
unsafe { std::str::from_utf8_unchecked(&initial_bytes[..chomped + delta]) };
while !state.bytes.is_empty() {
match peek_utf8_char_e(&state, TApply::End, TApply::Space) {
Ok((ch, bytes_parsed)) => {
// After the first character, only these are allowed:
//
// * Unicode alphabetic chars - you might name a variable `鹏` if that's clear to your readers
// * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric()
// * A dot ('.')
if ch.is_alphabetic() {
if part_buf.is_empty() && !ch.is_uppercase() {
// Each part must begin with a capital letter.
return Err((
MadeProgress,
TApply::StartNotUppercase(state.line, state.column),
state,
));
}
state = state.advance_without_indenting_ee(chomped, |r, c| {
TApply::Space(crate::parser::BadInputError::LineTooLong, r, c)
})?;
part_buf.push(ch);
} else if ch.is_ascii_digit() {
// Parts may not start with numbers!
if part_buf.is_empty() {
return Err((
MadeProgress,
TApply::StartIsNumber(state.line, state.column),
state,
));
}
dbg!(&state);
part_buf.push(ch);
} else if ch == '.' {
// Having two consecutive dots is an error.
if part_buf.is_empty() {
return Err((
MadeProgress,
TApply::DoubleDot(state.line, state.column),
state,
));
}
parts.push(part_buf.into_bump_str());
// Now that we've recorded the contents of the current buffer, reset it.
part_buf = String::new_in(arena);
} else {
// This must be the end of the type. We're done!
break;
}
state = state.advance_without_indenting_e(arena, bytes_parsed, TApply::Space)?;
}
Err(reason) => {
return Err((MadeProgress, reason, state));
}
Ok((MadeProgress, TypeAnnotation::Malformed(parsed_str), state))
}
}
if part_buf.is_empty() {
// We probably had a trailing dot, e.g. `Foo.bar.` - this is malformed!
//
// This condition might also occur if we encounter a malformed accessor like `.|`
//
// If we made it this far and don't have a next_char, then necessarily
// we have consumed a '.' char previously.
return Err((
MadeProgress,
TApply::TrailingDot(state.line, state.column),
state,
));
}
let answer = TypeAnnotation::Apply(
join_module_parts(arena, parts.into_bump_slice()),
part_buf.into_bump_str(),
&[],
);
Ok((MadeProgress, answer, state))
}
fn parse_type_variable<'a>(
arena: &'a Bump,
mut state: State<'a>,
) -> ParseResult<'a, TypeAnnotation<'a>, TVariable> {
let mut buf = String::new_in(arena);
state: State<'a>,
) -> ParseResult<'a, TypeAnnotation<'a>, Type<'a>> {
match crate::ident::lowercase_ident().parse(arena, state) {
Ok((_, name, state)) => {
let answer = TypeAnnotation::BoundVariable(name);
let start_bytes_len = state.bytes.len();
match peek_utf8_char_e(&state, TVariable::StartNotLowercase, TVariable::Space) {
Ok((first_letter, bytes_parsed)) => {
// Type variables must start with a lowercase letter.
if first_letter.is_alphabetic() && first_letter.is_lowercase() {
buf.push(first_letter);
} else {
return Err((
NoProgress,
TVariable::StartNotLowercase(state.line, state.column),
state,
));
}
state = state.advance_without_indenting_e(arena, bytes_parsed, TVariable::Space)?;
Ok((MadeProgress, answer, state))
}
Err(reason) => return Err((NoProgress, reason, state)),
Err((progress, _, state)) => Err((
progress,
Type::TBadTypeVariable(state.line, state.column),
state,
)),
}
while !state.bytes.is_empty() {
match peek_utf8_char_e(&state, TVariable::End, TVariable::Space) {
Ok((ch, bytes_parsed)) => {
// After the first character, only these are allowed:
//
// * Unicode alphabetic chars - you might name a variable `鹏` if that's clear to your readers
// * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric()
if ch.is_alphabetic() || ch.is_ascii_digit() {
buf.push(ch);
} else {
// This must be the end of the type. We're done!
break;
}
state = state.advance_without_indenting_e(arena, bytes_parsed, TVariable::Space)?;
}
Err(reason) => {
return state.fail(arena, MadeProgress, reason);
}
}
}
let answer = TypeAnnotation::BoundVariable(buf.into_bump_str());
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
Ok((progress, answer, state))
}

View File

@ -23,14 +23,12 @@ mod test_parse {
use roc_parse::ast::Pattern::{self, *};
use roc_parse::ast::StrLiteral::{self, *};
use roc_parse::ast::StrSegment::*;
use roc_parse::ast::{
self, Attempting, Def, EscapedChar, Spaceable, TypeAnnotation, WhenBranch,
};
use roc_parse::ast::{self, Def, EscapedChar, Spaceable, TypeAnnotation, WhenBranch};
use roc_parse::header::{
AppHeader, Effects, ExposesEntry, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageName, PackageOrPath, PlatformHeader, To,
};
use roc_parse::module::{app_header, interface_header, module_defs, platform_header};
use roc_parse::module::module_defs;
use roc_parse::parser::{Parser, State, SyntaxError};
use roc_parse::test_helpers::parse_expr_with;
use roc_region::all::{Located, Region};
@ -43,10 +41,9 @@ mod test_parse {
assert_eq!(Ok(expected_expr), actual);
}
fn assert_parsing_fails<'a>(input: &'a str, _reason: SyntaxError, _attempting: Attempting) {
fn assert_parsing_fails<'a>(input: &'a str, _reason: SyntaxError) {
let arena = Bump::new();
let actual = parse_expr_with(&arena, input);
// let expected_fail = Fail { reason, attempting };
assert!(actual.is_err());
}
@ -291,7 +288,7 @@ mod test_parse {
#[test]
fn empty_source_file() {
assert_parsing_fails("", SyntaxError::Eof(Region::zero()), Attempting::Module);
assert_parsing_fails("", SyntaxError::Eof(Region::zero()));
}
#[test]
@ -308,11 +305,7 @@ mod test_parse {
// Make sure it's longer than our maximum line length
assert_eq!(too_long_str.len(), max_line_length + 1);
assert_parsing_fails(
&too_long_str,
SyntaxError::LineTooLong(0),
Attempting::Module,
);
assert_parsing_fails(&too_long_str, SyntaxError::LineTooLong(0));
}
// INT LITERALS
@ -491,6 +484,29 @@ mod test_parse {
assert_eq!(Ok(expected), actual);
}
#[test]
fn var_minus_two() {
let arena = Bump::new();
let tuple = arena.alloc((
Located::new(
0,
0,
0,
1,
Var {
module_name: "",
ident: "x",
},
),
Located::new(0, 0, 1, 2, Minus),
Located::new(0, 0, 2, 3, Num("2")),
));
let expected = BinOp(tuple);
let actual = parse_expr_with(&arena, "x-2");
assert_eq!(Ok(expected), actual);
}
#[test]
fn add_with_spaces() {
let arena = Bump::new();
@ -624,6 +640,33 @@ mod test_parse {
assert_eq!(Ok(expected), actual);
}
#[test]
fn newline_and_spaces_before_less_than() {
let arena = Bump::new();
let spaced_int = arena.alloc(Num("1")).after(&[Newline]);
let tuple = arena.alloc((
Located::new(0, 0, 4, 5, spaced_int),
Located::new(1, 1, 4, 5, LessThan),
Located::new(1, 1, 6, 7, Num("2")),
));
let newlines = bumpalo::vec![in &arena; Newline, Newline];
let def = Def::Body(
arena.alloc(Located::new(0, 0, 0, 1, Identifier("x"))),
arena.alloc(Located::new(0, 1, 4, 7, BinOp(tuple))),
);
let loc_def = &*arena.alloc(Located::new(0, 1, 0, 7, def));
let defs = &[loc_def];
let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice());
let loc_ret = Located::new(3, 3, 0, 2, ret);
let expected = Defs(defs, arena.alloc(loc_ret));
// let expected = BinOp(tuple);
let actual = parse_expr_with(&arena, "x = 1\n < 2\n\n42");
assert_eq!(Ok(expected), actual);
}
#[test]
fn comment_with_non_ascii() {
let arena = Bump::new();
@ -975,22 +1018,25 @@ mod test_parse {
#[test]
fn qualified_global_tag() {
use roc_parse::ident::BadIdent;
let arena = Bump::new();
let expected = Expr::MalformedIdent("One.Two.Whee");
let expected = Expr::MalformedIdent("One.Two.Whee", BadIdent::QualifiedTag(0, 12));
let actual = parse_expr_with(&arena, "One.Two.Whee");
assert_eq!(Ok(expected), actual);
}
// TODO restore this test - it fails, but is not worth fixing right now.
// #[test]
// fn qualified_private_tag() {
// let arena = Bump::new();
// let expected = Expr::MalformedIdent("One.Two.@Whee");
// let actual = parse_expr_with(&arena, "One.Two.@Whee");
#[test]
fn private_qualified_tag() {
use roc_parse::ident::BadIdent;
// assert_eq!(Ok(expected), actual);
// }
let arena = Bump::new();
let expected = Expr::MalformedIdent("@One.Two.Whee", BadIdent::BadPrivateTag(0, 4));
let actual = parse_expr_with(&arena, "@One.Two.Whee");
assert_eq!(Ok(expected), actual);
}
#[test]
fn tag_pattern() {
@ -1003,15 +1049,6 @@ mod test_parse {
assert_eq!(Ok(expected), actual);
}
#[test]
fn private_qualified_tag() {
let arena = Bump::new();
let expected = Expr::MalformedIdent("@One.Two.Whee");
let actual = parse_expr_with(&arena, "@One.Two.Whee");
assert_eq!(Ok(expected), actual);
}
// LISTS
#[test]
@ -1304,21 +1341,18 @@ mod test_parse {
},
));
let args = &[&*arg1, &*arg2];
let apply_expr = Expr::Apply(
arena.alloc(Located::new(
0,
0,
1,
5,
Var {
module_name: "",
ident: "whee",
},
)),
args,
CalledVia::Space,
let function = Located::new(
0,
0,
1,
5,
Var {
module_name: "",
ident: "whee",
},
);
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 13, apply_expr)), loc_op);
let unary = Located::new(0, 0, 0, 5, UnaryOp(arena.alloc(function), loc_op));
let expected = Expr::Apply(arena.alloc(unary), args, CalledVia::Space);
let actual = parse_expr_with(&arena, "-whee 12 foo");
assert_eq!(Ok(expected), actual);
@ -1340,21 +1374,19 @@ mod test_parse {
},
));
let args = &[&*arg1, &*arg2];
let apply_expr = Expr::Apply(
arena.alloc(Located::new(
0,
0,
1,
5,
Var {
module_name: "",
ident: "whee",
},
)),
args,
CalledVia::Space,
let function = Located::new(
0,
0,
1,
5,
Var {
module_name: "",
ident: "whee",
},
);
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 13, apply_expr)), loc_op);
let unary = Located::new(0, 0, 0, 5, UnaryOp(arena.alloc(function), loc_op));
let expected = Expr::Apply(arena.alloc(unary), args, CalledVia::Space);
let actual = parse_expr_with(&arena, "!whee 12 foo");
assert_eq!(Ok(expected), actual);
@ -1390,7 +1422,7 @@ mod test_parse {
args,
CalledVia::Space,
)));
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 15, apply_expr)), loc_op);
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 2, 14, apply_expr)), loc_op);
let actual = parse_expr_with(&arena, "-(whee 12 foo)");
assert_eq!(Ok(expected), actual);
@ -1426,7 +1458,7 @@ mod test_parse {
args,
CalledVia::Space,
)));
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 15, apply_expr)), loc_op);
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 2, 14, apply_expr)), loc_op);
let actual = parse_expr_with(&arena, "!(whee 12 foo)");
assert_eq!(Ok(expected), actual);
@ -1506,9 +1538,21 @@ mod test_parse {
// underscore in an argument name, it would parse as three arguments
// (and would ignore the underscore as if it had been blank space).
let arena = Bump::new();
let pattern = Located::new(
0,
0,
1,
11,
Pattern::MalformedIdent(&"the_answer", roc_parse::ident::BadIdent::Underscore(0, 5)),
);
let patterns = &[pattern];
let expr = Located::new(0, 0, 15, 17, Expr::Num("42"));
let expected = Closure(patterns, &expr);
let actual = parse_expr_with(&arena, "\\the_answer -> 42");
assert_eq!(Ok(MalformedClosure), actual);
assert_eq!(Ok(expected), actual);
}
#[test]
@ -1726,6 +1770,120 @@ mod test_parse {
);
}
#[test]
fn one_backpassing() {
let arena = Bump::new();
let newlines = bumpalo::vec![in &arena; Newline, Newline];
let identifier_x = Located::new(1, 1, 0, 1, Identifier("x"));
let identifier_y = Located::new(1, 1, 7, 8, Identifier("y"));
let var_x = Var {
module_name: "",
ident: "x",
};
let var_y = Var {
module_name: "",
ident: "y",
};
let loc_var_y = arena.alloc(Located::new(1, 1, 12, 13, var_y));
let closure = ParensAround(arena.alloc(Closure(arena.alloc([identifier_y]), loc_var_y)));
let loc_closure = Located::new(1, 1, 5, 14, closure);
let ret = Expr::SpaceBefore(arena.alloc(var_x), newlines.into_bump_slice());
let loc_ret = Located::new(3, 3, 0, 1, ret);
let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")];
let expected = Expr::SpaceBefore(
arena.alloc(Expr::Backpassing(
arena.alloc([identifier_x]),
arena.alloc(loc_closure),
arena.alloc(loc_ret),
)),
reset_indentation.into_bump_slice(),
);
assert_parses_to(
indoc!(
r#"# leading comment
x <- (\y -> y)
x
"#
),
expected,
);
}
#[test]
fn two_backpassing() {
let arena = Bump::new();
let inner_backpassing = {
let identifier_z = Located::new(2, 2, 0, 1, Identifier("z"));
let empty_record = Record {
update: None,
fields: &[],
final_comments: &[],
};
let loc_empty_record = Located::new(2, 2, 5, 7, empty_record);
let var_x = Var {
module_name: "",
ident: "x",
};
let newlines = bumpalo::vec![in &arena; Newline, Newline];
let ret = Expr::SpaceBefore(arena.alloc(var_x), newlines.into_bump_slice());
let loc_ret = Located::new(4, 4, 0, 1, ret);
Expr::SpaceBefore(
arena.alloc(Expr::Backpassing(
arena.alloc([identifier_z]),
arena.alloc(loc_empty_record),
arena.alloc(loc_ret),
)),
arena.alloc([Newline]),
)
};
let identifier_x = Located::new(1, 1, 0, 1, Identifier("x"));
let identifier_y = Located::new(1, 1, 7, 8, Identifier("y"));
let var_y = Var {
module_name: "",
ident: "y",
};
let loc_var_y = arena.alloc(Located::new(1, 1, 12, 13, var_y));
let closure = ParensAround(arena.alloc(Closure(arena.alloc([identifier_y]), loc_var_y)));
let loc_closure = Located::new(1, 1, 5, 14, closure);
let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")];
let expected = Expr::SpaceBefore(
arena.alloc(Expr::Backpassing(
arena.alloc([identifier_x]),
arena.alloc(loc_closure),
arena.alloc(Located::new(2, 4, 0, 1, inner_backpassing)),
)),
reset_indentation.into_bump_slice(),
);
assert_parses_to(
indoc!(
r#"# leading comment
x <- (\y -> y)
z <- {}
x
"#
),
expected,
);
}
// #[test]
// fn type_signature_def() {
// let arena = Bump::new();
@ -2387,7 +2545,7 @@ mod test_parse {
let imports = Vec::new_in(&arena);
let provides = Vec::new_in(&arena);
let module_name = StrLiteral::PlainLine("test-app");
let expected = AppHeader {
let header = AppHeader {
name: Located::new(0, 0, 4, 14, module_name),
packages,
imports,
@ -2404,17 +2562,15 @@ mod test_parse {
after_to: &[],
};
let expected = roc_parse::ast::Module::App { header };
let src = indoc!(
r#"
app "test-app" packages {} imports [] provides [] to blah
"#
);
let actual = app_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2428,7 +2584,7 @@ mod test_parse {
let imports = Vec::new_in(&arena);
let provides = Vec::new_in(&arena);
let module_name = StrLiteral::PlainLine("test-app");
let expected = AppHeader {
let header = AppHeader {
name: Located::new(0, 0, 4, 14, module_name),
packages,
imports,
@ -2445,17 +2601,16 @@ mod test_parse {
after_to: &[],
};
let expected = roc_parse::ast::Module::App { header };
let src = indoc!(
r#"
app "test-app" provides [] to "./blah"
"#
);
let actual = app_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2480,7 +2635,8 @@ mod test_parse {
let provide_entry = Located::new(3, 3, 15, 24, Exposed("quicksort"));
let provides = bumpalo::vec![in &arena; provide_entry];
let module_name = StrLiteral::PlainLine("quicksort");
let expected = AppHeader {
let header = AppHeader {
name: Located::new(0, 0, 4, 15, module_name),
packages,
imports,
@ -2497,6 +2653,8 @@ mod test_parse {
after_to: &[],
};
let expected = roc_parse::ast::Module::App { header };
let src = indoc!(
r#"
app "quicksort"
@ -2506,12 +2664,8 @@ mod test_parse {
"#
);
let actual = app_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2531,7 +2685,7 @@ mod test_parse {
spaces_after_effects_keyword: &[],
spaces_after_type_name: &[],
};
let expected = PlatformHeader {
let header = PlatformHeader {
name: Located::new(0, 0, 9, 23, pkg_name),
requires: Vec::new_in(&arena),
exposes: Vec::new_in(&arena),
@ -2552,13 +2706,11 @@ mod test_parse {
after_provides: &[],
};
let expected = roc_parse::ast::Module::Platform { header };
let src = "platform rtfeldman/blah requires {} exposes [] packages {} imports [] provides [] effects fx.Blah {}";
let actual = platform_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2592,7 +2744,7 @@ mod test_parse {
spaces_after_effects_keyword: &[],
spaces_after_type_name: &[],
};
let expected = PlatformHeader {
let header = PlatformHeader {
name: Located::new(0, 0, 9, 19, pkg_name),
requires: Vec::new_in(&arena),
exposes: Vec::new_in(&arena),
@ -2613,6 +2765,8 @@ mod test_parse {
after_provides: &[],
};
let expected = roc_parse::ast::Module::Platform { header };
let src = indoc!(
r#"
platform foo/barbaz
@ -2624,12 +2778,8 @@ mod test_parse {
effects fx.Effect {}
"#
);
let actual = platform_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2640,7 +2790,7 @@ mod test_parse {
let exposes = Vec::new_in(&arena);
let imports = Vec::new_in(&arena);
let module_name = ModuleName::new("Foo");
let expected = InterfaceHeader {
let header = InterfaceHeader {
name: Located::new(0, 0, 10, 13, module_name),
exposes,
imports,
@ -2651,17 +2801,16 @@ mod test_parse {
before_imports: &[],
after_imports: &[],
};
let expected = roc_parse::ast::Module::Interface { header };
let src = indoc!(
r#"
interface Foo exposes [] imports []
"#
);
let actual = interface_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2672,7 +2821,7 @@ mod test_parse {
let exposes = Vec::new_in(&arena);
let imports = Vec::new_in(&arena);
let module_name = ModuleName::new("Foo.Bar.Baz");
let expected = InterfaceHeader {
let header = InterfaceHeader {
name: Located::new(0, 0, 10, 21, module_name),
exposes,
imports,
@ -2683,17 +2832,16 @@ mod test_parse {
before_imports: &[],
after_imports: &[],
};
let expected = roc_parse::ast::Module::Interface { header };
let src = indoc!(
r#"
interface Foo.Bar.Baz exposes [] imports []
"#
);
let actual = interface_header()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.0);
assert_eq!(Ok(expected), actual);
}
@ -2719,10 +2867,7 @@ mod test_parse {
"#
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.parse(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.1);
// It should occur twice in the debug output - once for the pattern,
@ -2781,10 +2926,7 @@ mod test_parse {
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.parse(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual);
@ -2804,11 +2946,8 @@ mod test_parse {
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
.parse(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.0);
assert!(actual.is_ok());
}
@ -2829,18 +2968,16 @@ mod test_parse {
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
.parse(&arena, State::new(src.as_bytes()))
.map(|tuple| tuple.0);
assert!(actual.is_ok());
}
#[test]
fn outdenting_newline_after_else() {
let arena = Bump::new();
let arena = &Bump::new();
// highlights a problem with the else branch demanding a newline after its expression
let src = indoc!(
@ -2852,16 +2989,19 @@ mod test_parse {
"#
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
dbg!(&actual);
assert!(actual.is_ok());
let state = State::new(src.as_bytes());
let parser = module_defs();
let parsed = parser.parse(arena, state);
match parsed {
Ok((_, _, state)) => {
dbg!(state);
return;
}
Err((_, _fail, _state)) => {
dbg!(_fail, _state);
assert!(false);
}
}
}
#[test]

View File

@ -133,7 +133,7 @@ pub enum RuntimeError {
region: Region,
},
InvalidPrecedence(PrecedenceProblem, Region),
MalformedIdentifier(Box<str>, Region),
MalformedIdentifier(Box<str>, roc_parse::ident::BadIdent, Region),
MalformedClosure(Region),
InvalidRecordUpdate {
region: Region,
@ -167,4 +167,5 @@ pub enum MalformedPatternProblem {
MalformedBase(Base),
Unknown,
QualifiedIdentifier,
BadIdent(roc_parse::ident::BadIdent),
}

View File

@ -3,7 +3,7 @@ use std::fmt;
/// TODO replace Located with this
pub type Loc<T> = Located<T>;
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Default)]
pub struct Region {
pub start_line: u32,
pub end_line: u32,
@ -21,7 +21,7 @@ impl Region {
}
}
pub fn new(start_line: u32, end_line: u32, start_col: u16, end_col: u16) -> Self {
pub const fn new(start_line: u32, end_line: u32, start_col: u16, end_col: u16) -> Self {
Self {
start_line,
start_col,
@ -107,6 +107,20 @@ impl Region {
end_line,
}
}
pub const fn start(&self) -> Position {
Position {
row: self.start_line,
col: self.start_col,
}
}
pub const fn end(&self) -> Position {
Position {
row: self.end_line,
col: self.end_col,
}
}
}
#[test]
@ -132,6 +146,12 @@ impl fmt::Debug for Region {
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Default)]
pub struct Position {
pub row: u32,
pub col: u16,
}
#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct Located<T> {
pub region: Region,

View File

@ -1,4 +1,5 @@
use roc_collections::all::MutSet;
use roc_parse::parser::{Col, Row};
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
use roc_problem::can::{FloatErrorKind, IntErrorKind, Problem, RuntimeError};
use roc_region::all::Region;
@ -305,24 +306,35 @@ pub fn can_problem<'b>(
alloc.reflow(" can occur in this position."),
]),
]),
Problem::InvalidHexadecimal(region) => {
todo!(
"TODO report an invalid hexadecimal number in a \\u(...) code point at region {:?}",
region
);
}
Problem::InvalidUnicodeCodePoint(region) => {
todo!(
"TODO report an invalid \\u(...) code point at region {:?}",
region
);
}
Problem::InvalidInterpolation(region) => {
todo!(
"TODO report an invalid string interpolation at region {:?}",
region
);
}
Problem::InvalidHexadecimal(region) => alloc.stack(vec![
alloc.reflow("This unicode code point is invalid:"),
alloc.region(region),
alloc.concat(vec![
alloc.reflow(r"I was expecting a hexadecimal number, like "),
alloc.parser_suggestion("\\u(1100)"),
alloc.reflow(" or "),
alloc.parser_suggestion("\\u(00FF)"),
alloc.text("."),
]),
alloc.reflow(r"Learn more about working with unicode in roc at TODO"),
]),
Problem::InvalidUnicodeCodePoint(region) => alloc.stack(vec![
alloc.reflow("This unicode code point is invalid:"),
alloc.region(region),
alloc.reflow("Learn more about working with unicode in roc at TODO"),
]),
Problem::InvalidInterpolation(region) => alloc.stack(vec![
alloc.reflow("This string interpolation is invalid:"),
alloc.region(region),
alloc.concat(vec![
alloc.reflow(r"I was expecting an identifier, like "),
alloc.parser_suggestion("\\u(message)"),
alloc.reflow(" or "),
alloc.parser_suggestion("\\u(LoremIpsum.text)"),
alloc.text("."),
]),
alloc.reflow(r"Learn more about string interpolation at TODO"),
]),
Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error),
};
@ -333,6 +345,296 @@ pub fn can_problem<'b>(
}
}
fn to_bad_ident_expr_report<'b>(
alloc: &'b RocDocAllocator<'b>,
bad_ident: roc_parse::ident::BadIdent,
surroundings: Region,
) -> RocDocBuilder<'b> {
use roc_parse::ident::BadIdent::*;
match bad_ident {
Start(_, _) | Space(_, _, _) => unreachable!("these are handled in the parser"),
WeirdDotAccess(row, col) | StrayDot(row, col) => {
let region = Region::from_row_col(row, col);
alloc.stack(vec![
alloc.reflow(r"I trying to parse a record field access here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("So I expect to see a lowercase letter next, like "),
alloc.parser_suggestion(".name"),
alloc.reflow(" or "),
alloc.parser_suggestion(".height"),
alloc.reflow("."),
]),
])
}
WeirdAccessor(_row, _col) => alloc.stack(vec![
alloc.reflow("I am very confused by this field access"),
alloc.region(surroundings),
alloc.concat(vec![
alloc.reflow("It looks like a field access on an accessor. I parse"),
alloc.parser_suggestion(".client.name"),
alloc.reflow(" as "),
alloc.parser_suggestion("(.client).name"),
alloc.reflow(". Maybe use an anonymous function like "),
alloc.parser_suggestion("(\\r -> r.client.name)"),
alloc.reflow(" instead"),
alloc.reflow("?"),
]),
]),
WeirdDotQualified(row, col) => {
let region = Region::from_row_col(row, col);
alloc.stack(vec![
alloc.reflow("I am trying to parse a qualified name here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I was expecting to see an identifier next, like "),
alloc.parser_suggestion("height"),
alloc.reflow(". A complete qualified name looks something like "),
alloc.parser_suggestion("Json.Decode.string"),
alloc.text("."),
]),
])
}
QualifiedTag(row, col) => {
let region = Region::from_row_col(row, col);
alloc.stack(vec![
alloc.reflow("I am trying to parse a qualified name here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow(r"This looks like a qualified tag name to me, "),
alloc.reflow(r"but tags cannot be qualified! "),
alloc.reflow(r"Maybe you wanted a qualified name, something like "),
alloc.parser_suggestion("Json.Decode.string"),
alloc.text("?"),
]),
])
}
Underscore(row, col) => {
let region =
Region::from_rows_cols(surroundings.start_line, surroundings.start_col, row, col);
alloc.stack(vec![
alloc.reflow("Underscores are not allowed in identifier names:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![alloc.reflow(
r"I recommend using camelCase, it is the standard in the Roc ecosystem.",
)]),
])
}
BadPrivateTag(row, col) => {
use BadIdentNext::*;
match what_is_next(alloc.src_lines, row, col) {
LowercaseAccess(width) => {
let region = Region::from_rows_cols(row, col, row, col + width);
alloc.stack(vec![
alloc.reflow("I am very confused by this field access:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow(r"It looks like a record field access on a private tag.")
]),
])
}
UppercaseAccess(width) => {
let region = Region::from_rows_cols(row, col, row, col + width);
alloc.stack(vec![
alloc.reflow("I am very confused by this expression:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow(
r"Looks like a private tag is treated like a module name. ",
),
alloc.reflow(r"Maybe you wanted a qualified name, like "),
alloc.parser_suggestion("Json.Decode.string"),
alloc.text("?"),
]),
])
}
Other(Some(c)) if c.is_lowercase() => {
let region = Region::from_rows_cols(
surroundings.start_line,
surroundings.start_col + 1,
row,
col + 1,
);
alloc.stack(vec![
alloc.reflow("I am trying to parse a private tag here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow(r"But after the "),
alloc.keyword("@"),
alloc.reflow(r" symbol I found a lowercase letter. "),
alloc.reflow(r"All tag names (global and private)"),
alloc.reflow(r" must start with an uppercase letter, like "),
alloc.parser_suggestion("@UUID"),
alloc.reflow(" or "),
alloc.parser_suggestion("@Secrets"),
alloc.reflow("."),
]),
])
}
other => todo!("{:?}", other),
}
}
}
}
fn to_bad_ident_pattern_report<'b>(
alloc: &'b RocDocAllocator<'b>,
bad_ident: roc_parse::ident::BadIdent,
surroundings: Region,
) -> RocDocBuilder<'b> {
use roc_parse::ident::BadIdent::*;
match bad_ident {
Start(_, _) | Space(_, _, _) => unreachable!("these are handled in the parser"),
WeirdDotAccess(row, col) | StrayDot(row, col) => {
let region = Region::from_row_col(row, col);
alloc.stack(vec![
alloc.reflow(r"I trying to parse a record field accessor here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("Something like "),
alloc.parser_suggestion(".name"),
alloc.reflow(" or "),
alloc.parser_suggestion(".height"),
alloc.reflow(" that accesses a value from a record."),
]),
])
}
WeirdAccessor(_row, _col) => alloc.stack(vec![
alloc.reflow("I am very confused by this field access"),
alloc.region(surroundings),
alloc.concat(vec![
alloc.reflow("It looks like a field access on an accessor. I parse"),
alloc.parser_suggestion(".client.name"),
alloc.reflow(" as "),
alloc.parser_suggestion("(.client).name"),
alloc.reflow(". Maybe use an anonymous function like "),
alloc.parser_suggestion("(\\r -> r.client.name)"),
alloc.reflow(" instead"),
alloc.reflow("?"),
]),
]),
WeirdDotQualified(row, col) => {
let region = Region::from_row_col(row, col);
alloc.stack(vec![
alloc.reflow("I am trying to parse a qualified name here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow("I was expecting to see an identifier next, like "),
alloc.parser_suggestion("height"),
alloc.reflow(". A complete qualified name looks something like "),
alloc.parser_suggestion("Json.Decode.string"),
alloc.text("."),
]),
])
}
QualifiedTag(row, col) => {
let region = Region::from_row_col(row, col);
alloc.stack(vec![
alloc.reflow("I am trying to parse a qualified name here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![
alloc.reflow(r"This looks like a qualified tag name to me, "),
alloc.reflow(r"but tags cannot be qualified! "),
alloc.reflow(r"Maybe you wanted a qualified name, something like "),
alloc.parser_suggestion("Json.Decode.string"),
alloc.text("?"),
]),
])
}
Underscore(row, col) => {
let region = Region::from_row_col(row, col - 1);
alloc.stack(vec![
alloc.reflow("I am trying to parse an identifier here:"),
alloc.region_with_subregion(surroundings, region),
alloc.concat(vec![alloc.reflow(
r"Underscores are not allowed in identifiers. Use camelCase instead!",
)]),
])
}
_ => todo!(),
}
}
#[derive(Debug)]
enum BadIdentNext<'a> {
LowercaseAccess(u16),
UppercaseAccess(u16),
NumberAccess(u16),
Keyword(&'a str),
DanglingDot,
Other(Option<char>),
}
fn what_is_next<'a>(source_lines: &'a [&'a str], row: Row, col: Col) -> BadIdentNext<'a> {
let row_index = row as usize;
let col_index = col as usize;
match source_lines.get(row_index) {
None => BadIdentNext::Other(None),
Some(line) => {
let chars = &line[col_index..];
let mut it = chars.chars();
match roc_parse::keyword::KEYWORDS
.iter()
.find(|keyword| crate::error::parse::starts_with_keyword(chars, keyword))
{
Some(keyword) => BadIdentNext::Keyword(keyword),
None => match it.next() {
None => BadIdentNext::Other(None),
Some('.') => match it.next() {
Some(c) if c.is_lowercase() => {
BadIdentNext::LowercaseAccess(2 + till_whitespace(it) as u16)
}
Some(c) if c.is_uppercase() => {
BadIdentNext::UppercaseAccess(2 + till_whitespace(it) as u16)
}
Some(c) if c.is_ascii_digit() => {
BadIdentNext::NumberAccess(2 + till_whitespace(it) as u16)
}
_ => BadIdentNext::DanglingDot,
},
Some(c) => BadIdentNext::Other(Some(c)),
},
}
}
}
}
fn till_whitespace<I>(it: I) -> usize
where
I: Iterator<Item = char>,
{
let mut chomped = 0;
for c in it {
if c.is_ascii_whitespace() || c == '#' {
break;
} else {
chomped += 1;
continue;
}
}
chomped
}
fn pretty_runtime_error<'b>(
alloc: &'b RocDocAllocator<'b>,
runtime_error: RuntimeError,
@ -421,6 +723,7 @@ fn pretty_runtime_error<'b>(
MalformedBase(Base::Binary) => " binary integer ",
MalformedBase(Base::Octal) => " octal integer ",
MalformedBase(Base::Decimal) => " integer ",
BadIdent(bad_ident) => return to_bad_ident_pattern_report(alloc, bad_ident, region),
Unknown => " ",
QualifiedIdentifier => " qualified ",
};
@ -429,7 +732,7 @@ fn pretty_runtime_error<'b>(
MalformedInt | MalformedFloat | MalformedBase(_) => alloc
.tip()
.append(alloc.reflow("Learn more about number literals at TODO")),
Unknown => alloc.nil(),
Unknown | BadIdent(_) => alloc.nil(),
QualifiedIdentifier => alloc.tip().append(
alloc.reflow("In patterns, only private and global tags can be qualified"),
),
@ -471,15 +774,10 @@ fn pretty_runtime_error<'b>(
// do nothing, reported with PrecedenceProblem
unreachable!()
}
RuntimeError::MalformedIdentifier(box_str, region) => {
alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("The ")
.append(format!("`{}`", box_str))
.append(alloc.reflow(" identifier is malformed:")),
]),
alloc.region(region),
])
RuntimeError::MalformedIdentifier(_box_str, bad_ident, surroundings) => {
to_bad_ident_expr_report(alloc, bad_ident, surroundings)
}
RuntimeError::MalformedClosure(_) => todo!(""),
RuntimeError::InvalidFloat(sign @ FloatErrorKind::PositiveInfinity, region, _raw_str)

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More