Merge remote-tracking branch 'origin/trunk' into task

This commit is contained in:
Folkert 2020-12-08 14:51:46 +01:00
commit 4c2654d4ed
84 changed files with 3514 additions and 2186 deletions

697
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -13,7 +13,7 @@ use target_lexicon::Triple;
fn report_timing(buf: &mut String, label: &str, duration: Duration) {
buf.push_str(&format!(
" {:.3} ms {}\n",
" {:9.3} ms {}\n",
duration.as_secs_f64() * 1000.0,
label,
));
@ -45,6 +45,9 @@ pub fn build_file(
src_dir.as_path(),
subs_by_module,
)?;
let path_to_platform = loaded.platform_path.clone();
let app_o_file = roc_file_path.with_file_name("roc_app.o");
let buf = &mut String::with_capacity(1024);
@ -53,7 +56,14 @@ pub fn build_file(
let module_name = loaded.interns.module_name(*module_id);
buf.push_str(" ");
if module_name.is_empty() {
// the App module
buf.push_str("Application Module");
} else {
buf.push_str(module_name);
}
buf.push('\n');
report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file);
@ -81,15 +91,10 @@ pub fn build_file(
}
}
println!(
"\n\nCompilation finished! Here's how long each module took to compile:\n\n{}",
buf
);
let cwd = app_o_file.parent().unwrap();
let binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows
program::gen_from_mono_module(
let code_gen_timing = program::gen_from_mono_module(
&arena,
loaded,
roc_file_path,
@ -99,6 +104,19 @@ pub fn build_file(
emit_debug_info,
);
buf.push('\n');
buf.push_str(" ");
buf.push_str("Code Generation");
buf.push('\n');
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();
@ -112,7 +130,9 @@ pub fn build_file(
);
// Step 2: link the precompiled host and compiled app
let host_input_path = cwd.join("platform").join("host.o");
let mut host_input_path = PathBuf::from(cwd);
host_input_path.push(&*path_to_platform);
host_input_path.push("host.o");
// TODO we should no longer need to do this once we have platforms on
// a package repository, as we can then get precompiled hosts from there.

View file

@ -54,12 +54,12 @@ mod repl_eval {
#[test]
fn literal_0x0() {
expect_success("0x0", "0 : Int");
expect_success("0x0", "0 : I64");
}
#[test]
fn literal_0x42() {
expect_success("0x42", "66 : Int");
expect_success("0x42", "66 : I64");
}
#[test]
@ -79,7 +79,7 @@ mod repl_eval {
#[test]
fn int_addition() {
expect_success("0x1 + 2", "3 : Int");
expect_success("0x1 + 2", "3 : I64");
}
#[test]
@ -89,7 +89,7 @@ mod repl_eval {
#[test]
fn num_rem() {
expect_success("299 % 10", "Ok 9 : Result Int [ DivByZero ]*");
expect_success("299 % 10", "Ok 9 : Result I64 [ DivByZero ]*");
}
#[test]
@ -191,7 +191,7 @@ mod repl_eval {
#[test]
fn str_count_graphemes() {
expect_success("Str.countGraphemes \"å🤔\"", "2 : Int");
expect_success("Str.countGraphemes \"å🤔\"", "2 : I64");
}
#[test]
@ -206,7 +206,7 @@ mod repl_eval {
#[test]
fn literal_int_list() {
expect_success("[ 0x1, 0x2, 0x3 ]", "[ 1, 2, 3 ] : List Int");
expect_success("[ 0x1, 0x2, 0x3 ]", "[ 1, 2, 3 ] : List I64");
}
#[test]
@ -239,7 +239,7 @@ mod repl_eval {
fn nested_int_list() {
expect_success(
r#"[ [ [ 4, 3, 2 ], [ 1, 0x0 ] ], [ [] ], [] ]"#,
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List Int))"#,
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List I64))"#,
);
}
@ -316,7 +316,7 @@ mod repl_eval {
fn basic_2_field_i64_record() {
expect_success(
"{ foo: 0x4, bar: 0x2 }",
"{ bar: 2, foo: 4 } : { bar : Int, foo : Int }",
"{ bar: 2, foo: 4 } : { bar : I64, foo : I64 }",
);
}
@ -340,7 +340,7 @@ mod repl_eval {
fn basic_3_field_record() {
expect_success(
"{ foo: 4.1, bar: 2, baz: 0x5 }",
"{ bar: 2, baz: 5, foo: 4.1 } : { bar : Num *, baz : Int, foo : F64 }",
"{ bar: 2, baz: 5, foo: 4.1 } : { bar : Num *, baz : I64, foo : F64 }",
);
}
@ -387,7 +387,7 @@ mod repl_eval {
fn list_of_3_field_records() {
expect_success(
"[ { foo: 4.1, bar: 2, baz: 0x3 } ]",
"[ { bar: 2, baz: 3, foo: 4.1 } ] : List { bar : Num *, baz : Int, foo : F64 }",
"[ { bar: 2, baz: 3, foo: 4.1 } ] : List { bar : Num *, baz : I64, foo : F64 }",
);
}

View file

@ -93,7 +93,7 @@ pub fn rebuild_host(host_input_path: &Path) {
.output()
.unwrap();
validate_output("host.rs", "cargo build --release", output);
validate_output("src/lib.rs", "cargo build --release", output);
let output = Command::new("ld")
.env_clear()

View file

@ -7,8 +7,15 @@ use roc_gen::llvm::build::{build_proc, build_proc_header, module_from_builtins,
use roc_load::file::MonomorphizedModule;
use roc_mono::layout::LayoutIds;
use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime};
use target_lexicon::Triple;
#[derive(Debug, Clone, Copy, Default)]
pub struct CodeGenTiming {
pub code_gen: Duration,
pub emit_o_file: Duration,
}
// TODO how should imported modules factor into this? What if those use builtins too?
// TODO this should probably use more helper functions
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
@ -21,11 +28,13 @@ pub fn gen_from_mono_module(
app_o_file: &Path,
opt_level: OptLevel,
emit_debug_info: bool,
) {
) -> CodeGenTiming {
use roc_reporting::report::{
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
};
let code_gen_start = SystemTime::now();
for (home, (module_path, src)) in loaded.sources {
let src_lines: Vec<&str> = src.split('\n').collect();
let palette = DEFAULT_PALETTE;
@ -160,6 +169,9 @@ pub fn gen_from_mono_module(
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
let code_gen = code_gen_start.elapsed().unwrap();
let emit_o_file_start = SystemTime::now();
// annotate the LLVM IR output with debug info
// so errors are reported with the line number of the LLVM source
if emit_debug_info {
@ -232,6 +244,13 @@ pub fn gen_from_mono_module(
.write_to_file(&env.module, FileType::Object, &app_o_file)
.expect("Writing .o file failed");
}
let emit_o_file = emit_o_file_start.elapsed().unwrap();
CodeGenTiming {
emit_o_file,
code_gen,
}
}
pub struct FunctionIterator<'ctx> {

1
compiler/builtins/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
builtins.ll

View file

@ -9,16 +9,13 @@ pub fn build(b: *Builder) void {
// Options
const fallback_main_path = "./src/main.zig";
const main_path_desc = b.fmt("Override path to main.zig. Used by \"ir\", \"bc\", and \"test\". Defaults to \"{}\". ", .{fallback_main_path});
const main_path_desc = b.fmt("Override path to main.zig. Used by \"ir\" and \"test\". Defaults to \"{}\". ", .{fallback_main_path});
const main_path = b.option([]const u8, "main-path", main_path_desc) orelse fallback_main_path;
const fallback_bitcode_path = "./builtins.bc";
const bitcode_path_desc = b.fmt("Override path to generated bitcode file. Used by \"ir\" and \"bc\". Defaults to \"{}\". ", .{fallback_bitcode_path});
const bitcode_path = b.option([]const u8, "bc-path", bitcode_path_desc) orelse fallback_bitcode_path;
// Tests
var main_tests = b.addTest(main_path);
main_tests.setBuildMode(mode);
main_tests.linkSystemLibrary("c");
const test_step = b.step("test", "Run tests");
test_step.dependOn(&main_tests.step);
@ -26,25 +23,13 @@ pub fn build(b: *Builder) void {
const obj_name = "builtins";
const obj = b.addObject(obj_name, main_path);
obj.setBuildMode(mode);
obj.linkSystemLibrary("c");
obj.strip = true;
obj.emit_llvm_ir = true;
obj.emit_bin = false;
const ir = b.step("ir", "Build LLVM ir");
ir.dependOn(&obj.step);
// IR to Bitcode
const bitcode_path_arg = b.fmt("-o={}", .{bitcode_path});
const ir_out_file = b.fmt("{}.ll", .{obj_name});
const ir_to_bitcode = b.addSystemCommand(&[_][]const u8{
"llvm-as-10",
ir_out_file,
bitcode_path_arg,
});
const bicode = b.step("bc", "Build LLVM ir and convert to bitcode");
bicode.dependOn(ir);
bicode.dependOn(&ir_to_bitcode.step);
b.default_step = ir;
removeInstallSteps(b);
}

View file

@ -3,7 +3,7 @@
set -euxo pipefail
# Test every zig
find src/*.zig -type f -print0 | xargs -n 1 -0 zig test --library c
zig build test
# fmt every zig
find src/*.zig -type f -print0 | xargs -n 1 -0 zig fmt --check

View file

@ -4,7 +4,7 @@ const expectEqual = std.testing.expectEqual;
// This whole module is a translation of grapheme breaks from
// the https://github.com/JuliaStrings/utf8proc library.
// Thanks so much to those developer!
// Thanks so much to those developers!
//
// The only function this file exposes is `isGraphemeBreak`
//

View file

@ -15,25 +15,25 @@ comptime {
// Str Module
const str = @import("str.zig");
comptime {
exportStrFn(str.strSplitInPlace, "str_split_in_place");
exportStrFn(str.strSplitInPlaceC, "str_split_in_place");
exportStrFn(str.countSegments, "count_segments");
exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters");
exportStrFn(str.startsWith, "starts_with");
exportStrFn(str.endsWith, "ends_with");
exportStrFn(str.strConcat, "concat");
exportStrFn(str.strConcatC, "concat");
exportStrFn(str.strNumberOfBytes, "number_of_bytes");
exportStrFn(str.strFromInt, "from_int");
exportStrFn(str.strFromIntC, "from_int");
}
// Export helpers - Must be run inside a comptime
fn exportBuiltinFn(comptime fn_target: anytype, comptime fn_name: []const u8) void {
@export(fn_target, .{ .name = "roc_builtins." ++ fn_name, .linkage = .Strong });
fn exportBuiltinFn(comptime func: anytype, comptime func_name: []const u8) void {
@export(func, .{ .name = "roc_builtins." ++ func_name, .linkage = .Strong });
}
fn exportNumFn(comptime fn_target: anytype, comptime fn_name: []const u8) void {
exportBuiltinFn(fn_target, "num." ++ fn_name);
fn exportNumFn(comptime func: anytype, comptime func_name: []const u8) void {
exportBuiltinFn(func, "num." ++ func_name);
}
fn exportStrFn(comptime fn_target: anytype, comptime fn_name: []const u8) void {
exportBuiltinFn(fn_target, "str." ++ fn_name);
fn exportStrFn(comptime func: anytype, comptime func_name: []const u8) void {
exportBuiltinFn(func, "str." ++ func_name);
}
// Run all tests in imported modules

View file

@ -1,36 +1,35 @@
const std = @import("std");
const mem = std.mem;
const Allocator = mem.Allocator;
const unicode = std.unicode;
const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
extern fn malloc(size: usize) ?*u8;
extern fn free([*]u8) void;
const RocStr = extern struct {
str_bytes: ?[*]u8,
str_len: usize,
pub fn empty() RocStr {
pub inline fn empty() RocStr {
return RocStr{
.str_len = 0,
.str_bytes = null,
};
}
// This takes ownership of the pointed-to bytes if they won't fit in a
// This clones 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 init(bytes: [*]const u8, length: usize) RocStr {
const rocStrSize = @sizeOf(RocStr);
pub fn init(allocator: *Allocator, bytes_ptr: [*]const u8, length: usize) RocStr {
const roc_str_size = @sizeOf(RocStr);
if (length < rocStrSize) {
if (length < roc_str_size) {
var ret_small_str = RocStr.empty();
const target_ptr = @ptrToInt(&ret_small_str);
var index: u8 = 0;
// TODO isn't there a way to bulk-zero data in Zig?
// Zero out the data, just to be safe
while (index < rocStrSize) {
while (index < roc_str_size) {
var offset_ptr = @intToPtr(*u8, target_ptr + index);
offset_ptr.* = 0;
index += 1;
@ -40,19 +39,19 @@ const RocStr = extern struct {
index = 0;
while (index < length) {
var offset_ptr = @intToPtr(*u8, target_ptr + index);
offset_ptr.* = bytes[index];
offset_ptr.* = bytes_ptr[index];
index += 1;
}
// set the final byte to be the length
const final_byte_ptr = @intToPtr(*u8, target_ptr + rocStrSize - 1);
const final_byte_ptr = @intToPtr(*u8, target_ptr + roc_str_size - 1);
final_byte_ptr.* = @truncate(u8, length) ^ 0b10000000;
return ret_small_str;
} else {
var result = allocate_str(u64, InPlace.Clone, length);
var result = allocateStr(allocator, u64, InPlace.Clone, length);
@memcpy(@ptrCast([*]u8, result.str_bytes), bytes, length);
@memcpy(@ptrCast([*]u8, result.str_bytes), bytes_ptr, length);
return result;
}
@ -61,25 +60,28 @@ const RocStr = extern struct {
// 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 {
const rocStrSize = @sizeOf(RocStr);
const roc_str_size = @sizeOf(RocStr);
if (length < rocStrSize) {
if (length < roc_str_size) {
return RocStr.empty();
} else {
var new_bytes: [*]u8 = @ptrCast([*]u8, malloc(length));
var new_bytes: []T = allocator.alloc(u8, length) catch unreachable;
var new_bytes_ptr: [*]u8 = @ptrCast([*]u8, &new_bytes);
return RocStr{
.str_bytes = new_bytes,
.str_bytes = new_bytes_ptr,
.str_len = length,
};
}
}
pub fn drop(self: RocStr) void {
if (!self.is_small_str()) {
const str_bytes: [*]u8 = self.str_bytes orelse unreachable;
pub fn deinit(self: RocStr, allocator: *Allocator) void {
if (!self.isSmallStr() and !self.isEmpty()) {
const str_bytes_ptr: [*]u8 = self.str_bytes orelse unreachable;
free(str_bytes);
const str_bytes: []u8 = str_bytes_ptr[0..self.str_len];
allocator.free(str_bytes);
}
}
@ -102,8 +104,8 @@ const RocStr = extern struct {
const self_u8_ptr: [*]const u8 = @ptrCast([*]const u8, &self);
const other_u8_ptr: [*]const u8 = @ptrCast([*]const u8, &other);
const self_bytes: [*]const u8 = if (self.is_small_str() or self.is_empty()) self_u8_ptr else self_bytes_ptr orelse unreachable;
const other_bytes: [*]const u8 = if (other.is_small_str() or other.is_empty()) other_u8_ptr else other_bytes_ptr orelse unreachable;
const self_bytes: [*]const u8 = if (self.isSmallStr() or self.isEmpty()) self_u8_ptr else self_bytes_ptr orelse unreachable;
const other_bytes: [*]const u8 = if (other.isSmallStr() or other.isEmpty()) other_u8_ptr else other_bytes_ptr orelse unreachable;
var index: usize = 0;
@ -120,7 +122,7 @@ const RocStr = extern struct {
return true;
}
pub fn is_small_str(self: RocStr) bool {
pub fn isSmallStr(self: RocStr) bool {
return @bitCast(isize, self.str_len) < 0;
}
@ -132,17 +134,17 @@ const RocStr = extern struct {
// Since this conditional would be prone to branch misprediction,
// make sure it will compile to a cmov.
return if (self.is_small_str()) small_len else big_len;
return if (self.isSmallStr()) small_len else big_len;
}
pub fn is_empty(self: RocStr) bool {
pub fn isEmpty(self: RocStr) bool {
return self.len() == 0;
}
pub fn as_u8_ptr(self: RocStr) [*]u8 {
pub fn asU8ptr(self: RocStr) [*]u8 {
const if_small = &@bitCast([16]u8, self);
const if_big = @ptrCast([*]u8, self.str_bytes);
return if (self.is_small_str() or self.is_empty()) if_small else if_big;
return if (self.isSmallStr() or self.isEmpty()) if_small else if_big;
}
// Given a pointer to some bytes, write the first (len) bytes of this
@ -160,7 +162,7 @@ const RocStr = extern struct {
// Since this conditional would be prone to branch misprediction,
// make sure it will compile to a cmov.
const src: [*]u8 = if (self.is_small_str()) small_src else big_src;
const src: [*]u8 = if (self.isSmallStr()) small_src else big_src;
@memcpy(dest, src, len);
}
@ -169,70 +171,77 @@ const RocStr = extern struct {
const str1_len = 3;
var str1: [str1_len]u8 = "abc".*;
const str1_ptr: [*]u8 = &str1;
var roc_str1 = RocStr.init(str1_ptr, str1_len);
var roc_str1 = RocStr.init(testing.allocator, str1_ptr, str1_len);
const str2_len = 3;
var str2: [str2_len]u8 = "abc".*;
const str2_ptr: [*]u8 = &str2;
var roc_str2 = RocStr.init(str2_ptr, str2_len);
var roc_str2 = RocStr.init(testing.allocator, str2_ptr, str2_len);
// TODO: fix those tests
// expect(roc_str1.eq(roc_str2));
roc_str1.drop();
roc_str2.drop();
roc_str1.deinit(testing.allocator);
roc_str2.deinit(testing.allocator);
}
test "RocStr.eq: not equal different length" {
const str1_len = 4;
var str1: [str1_len]u8 = "abcd".*;
const str1_ptr: [*]u8 = &str1;
var roc_str1 = RocStr.init(str1_ptr, str1_len);
var roc_str1 = RocStr.init(testing.allocator, str1_ptr, str1_len);
const str2_len = 3;
var str2: [str2_len]u8 = "abc".*;
const str2_ptr: [*]u8 = &str2;
var roc_str2 = RocStr.init(str2_ptr, str2_len);
var roc_str2 = RocStr.init(testing.allocator, str2_ptr, str2_len);
defer {
roc_str1.deinit(testing.allocator);
roc_str2.deinit(testing.allocator);
}
expect(!roc_str1.eq(roc_str2));
roc_str1.drop();
roc_str2.drop();
}
test "RocStr.eq: not equal same length" {
const str1_len = 3;
var str1: [str1_len]u8 = "acb".*;
const str1_ptr: [*]u8 = &str1;
var roc_str1 = RocStr.init(str1_ptr, str1_len);
var roc_str1 = RocStr.init(testing.allocator, str1_ptr, str1_len);
const str2_len = 3;
var str2: [str2_len]u8 = "abc".*;
const str2_ptr: [*]u8 = &str2;
var roc_str2 = RocStr.init(str2_ptr, str2_len);
var roc_str2 = RocStr.init(testing.allocator, str2_ptr, str2_len);
defer {
roc_str1.deinit(testing.allocator);
roc_str2.deinit(testing.allocator);
}
// TODO: fix those tests
// expect(!roc_str1.eq(roc_str2));
roc_str1.drop();
roc_str2.drop();
}
};
// Str.numberOfBytes
pub fn strNumberOfBytes(string: RocStr) callconv(.C) usize {
return string.len();
}
// Str.fromInt
pub fn strFromInt(int: i64) callconv(.C) RocStr {
// prepare for having multiple integer types in the future
return strFromIntHelp(i64, int);
// When we actually use this in Roc, libc will be linked so we have access to std.heap.c_allocator
pub fn strFromIntC(int: i64) callconv(.C) RocStr {
return strFromInt(std.heap.c_allocator, int);
}
fn strFromIntHelp(comptime T: type, int: T) RocStr {
inline fn strFromInt(allocator: *Allocator, int: i64) RocStr {
// prepare for having multiple integer types in the future
return strFromIntHelp(allocator, i64, int);
}
fn strFromIntHelp(allocator: *Allocator, comptime T: type, int: T) RocStr {
// determine maximum size for this T
comptime const size = comptime blk: {
// the string representation of the minimum i128 value uses at most 40 characters
@ -244,20 +253,24 @@ fn strFromIntHelp(comptime T: type, int: T) RocStr {
var buf: [size]u8 = undefined;
const result = std.fmt.bufPrint(&buf, "{}", .{int}) catch unreachable;
return RocStr.init(&buf, result.len);
return RocStr.init(allocator, &buf, result.len);
}
// 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 {
strSplitInPlace(std.heap.c_allocator, array, string, delimiter);
}
pub fn strSplitInPlace(array: [*]RocStr, array_len: usize, string: RocStr, delimiter: RocStr) callconv(.C) void {
inline fn strSplitInPlace(allocator: *Allocator, array: [*]RocStr, string: RocStr, delimiter: RocStr) void {
var ret_array_index: usize = 0;
var sliceStart_index: usize = 0;
var slice_start_index: usize = 0;
var str_index: usize = 0;
const str_bytes = string.as_u8_ptr();
const str_bytes = string.asU8ptr();
const str_len = string.len();
const delimiter_bytes_ptrs = delimiter.as_u8_ptr();
const delimiter_bytes_ptrs = delimiter.asU8ptr();
const delimiter_len = delimiter.len();
if (str_len > delimiter_len) {
@ -279,10 +292,10 @@ pub fn strSplitInPlace(array: [*]RocStr, array_len: usize, string: RocStr, delim
}
if (matches_delimiter) {
const segment_len: usize = str_index - sliceStart_index;
const segment_len: usize = str_index - slice_start_index;
array[ret_array_index] = RocStr.init(str_bytes + sliceStart_index, segment_len);
sliceStart_index = str_index + delimiter_len;
array[ret_array_index] = RocStr.init(allocator, str_bytes + slice_start_index, segment_len);
slice_start_index = str_index + delimiter_len;
ret_array_index += 1;
str_index += delimiter_len;
} else {
@ -291,44 +304,49 @@ pub fn strSplitInPlace(array: [*]RocStr, array_len: usize, string: RocStr, delim
}
}
array[ret_array_index] = RocStr.init(str_bytes + sliceStart_index, str_len - sliceStart_index);
array[ret_array_index] = RocStr.init(allocator, str_bytes + slice_start_index, str_len - slice_start_index);
}
test "strSplitInPlace: no delimiter" {
// Str.split "abc" "!" == [ "abc" ]
const str_arr = "abc";
const str = RocStr.init(str_arr, str_arr.len);
const str = RocStr.init(testing.allocator, str_arr, str_arr.len);
const delimiter_arr = "!";
const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len);
var array: [1]RocStr = undefined;
const array_ptr: [*]RocStr = &array;
strSplitInPlace(array_ptr, 1, str, delimiter);
strSplitInPlace(testing.allocator, array_ptr, str, delimiter);
var expected = [1]RocStr{
str,
};
expectEqual(array.len, expected.len);
expect(array[0].eq(expected[0]));
defer {
for (array) |roc_str| {
roc_str.drop();
roc_str.deinit(testing.allocator);
}
for (expected) |roc_str| {
roc_str.drop();
roc_str.deinit(testing.allocator);
}
str.deinit(testing.allocator);
delimiter.deinit(testing.allocator);
}
expectEqual(array.len, expected.len);
expect(array[0].eq(expected[0]));
}
test "strSplitInPlace: empty end" {
const str_arr = "1---- ---- ---- ---- ----2---- ---- ---- ---- ----";
const str = RocStr.init(str_arr, str_arr.len);
const str = RocStr.init(testing.allocator, str_arr, str_arr.len);
const delimiter_arr = "---- ---- ---- ---- ----";
const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len);
const array_len: usize = 3;
var array: [array_len]RocStr = [_]RocStr{
@ -338,15 +356,28 @@ test "strSplitInPlace: empty end" {
};
const array_ptr: [*]RocStr = &array;
strSplitInPlace(array_ptr, array_len, str, delimiter);
strSplitInPlace(testing.allocator, array_ptr, str, delimiter);
const one = RocStr.init("1", 1);
const two = RocStr.init("2", 1);
const one = RocStr.init(testing.allocator, "1", 1);
const two = RocStr.init(testing.allocator, "2", 1);
var expected = [3]RocStr{
one, two, RocStr.empty(),
};
defer {
for (array) |rocStr| {
rocStr.deinit(testing.allocator);
}
for (expected) |rocStr| {
rocStr.deinit(testing.allocator);
}
str.deinit(testing.allocator);
delimiter.deinit(testing.allocator);
}
expectEqual(array.len, expected.len);
expect(array[0].eq(expected[0]));
expect(array[1].eq(expected[1]));
@ -355,10 +386,10 @@ test "strSplitInPlace: empty end" {
test "strSplitInPlace: delimiter on sides" {
const str_arr = "tttghittt";
const str = RocStr.init(str_arr, str_arr.len);
const str = RocStr.init(testing.allocator, str_arr, str_arr.len);
const delimiter_arr = "ttt";
const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len);
const array_len: usize = 3;
var array: [array_len]RocStr = [_]RocStr{
@ -367,15 +398,28 @@ test "strSplitInPlace: delimiter on sides" {
undefined,
};
const array_ptr: [*]RocStr = &array;
strSplitInPlace(array_ptr, array_len, str, delimiter);
strSplitInPlace(testing.allocator, array_ptr, str, delimiter);
const ghi_arr = "ghi";
const ghi = RocStr.init(ghi_arr, ghi_arr.len);
const ghi = RocStr.init(testing.allocator, ghi_arr, ghi_arr.len);
var expected = [3]RocStr{
RocStr.empty(), ghi, RocStr.empty(),
};
defer {
for (array) |rocStr| {
rocStr.deinit(testing.allocator);
}
for (expected) |rocStr| {
rocStr.deinit(testing.allocator);
}
str.deinit(testing.allocator);
delimiter.deinit(testing.allocator);
}
expectEqual(array.len, expected.len);
expect(array[0].eq(expected[0]));
expect(array[1].eq(expected[1]));
@ -385,25 +429,38 @@ test "strSplitInPlace: delimiter on sides" {
test "strSplitInPlace: three pieces" {
// Str.split "a!b!c" "!" == [ "a", "b", "c" ]
const str_arr = "a!b!c";
const str = RocStr.init(str_arr, str_arr.len);
const str = RocStr.init(testing.allocator, str_arr, str_arr.len);
const delimiter_arr = "!";
const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len);
const array_len: usize = 3;
var array: [array_len]RocStr = undefined;
const array_ptr: [*]RocStr = &array;
strSplitInPlace(array_ptr, array_len, str, delimiter);
strSplitInPlace(testing.allocator, array_ptr, str, delimiter);
const a = RocStr.init("a", 1);
const b = RocStr.init("b", 1);
const c = RocStr.init("c", 1);
const a = RocStr.init(testing.allocator, "a", 1);
const b = RocStr.init(testing.allocator, "b", 1);
const c = RocStr.init(testing.allocator, "c", 1);
var expected_array = [array_len]RocStr{
a, b, c,
};
defer {
for (array) |roc_str| {
roc_str.deinit(testing.allocator);
}
for (expected_array) |roc_str| {
roc_str.deinit(testing.allocator);
}
str.deinit(testing.allocator);
delimiter.deinit(testing.allocator);
}
expectEqual(expected_array.len, array.len);
expect(array[0].eq(expected_array[0]));
expect(array[1].eq(expected_array[1]));
@ -415,10 +472,10 @@ test "strSplitInPlace: three pieces" {
// needs to be broken into, so that we can allocate a array
// of that size. It always returns at least 1.
pub fn countSegments(string: RocStr, delimiter: RocStr) callconv(.C) usize {
const str_bytes = string.as_u8_ptr();
const str_bytes = string.asU8ptr();
const str_len = string.len();
const delimiter_bytes_ptrs = delimiter.as_u8_ptr();
const delimiter_bytes_ptrs = delimiter.asU8ptr();
const delimiter_len = delimiter.len();
var count: usize = 1;
@ -459,13 +516,17 @@ test "countSegments: long delimiter" {
// Str.split "str" "delimiter" == [ "str" ]
// 1 segment
const str_arr = "str";
const str = RocStr.init(str_arr, str_arr.len);
const str = RocStr.init(testing.allocator, str_arr, str_arr.len);
const delimiter_arr = "delimiter";
const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len);
defer {
str.deinit(testing.allocator);
delimiter.deinit(testing.allocator);
}
const segments_count = countSegments(str, delimiter);
expectEqual(segments_count, 1);
}
@ -473,10 +534,15 @@ test "countSegments: delimiter at start" {
// Str.split "hello there" "hello" == [ "", " there" ]
// 2 segments
const str_arr = "hello there";
const str = RocStr.init(str_arr, str_arr.len);
const str = RocStr.init(testing.allocator, str_arr, str_arr.len);
const delimiter_arr = "hello";
const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len);
defer {
str.deinit(testing.allocator);
delimiter.deinit(testing.allocator);
}
const segments_count = countSegments(str, delimiter);
@ -487,10 +553,15 @@ test "countSegments: delimiter interspered" {
// Str.split "a!b!c" "!" == [ "a", "b", "c" ]
// 3 segments
const str_arr = "a!b!c";
const str = RocStr.init(str_arr, str_arr.len);
const str = RocStr.init(testing.allocator, str_arr, str_arr.len);
const delimiter_arr = "!";
const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len);
defer {
str.deinit(testing.allocator);
delimiter.deinit(testing.allocator);
}
const segments_count = countSegments(str, delimiter);
@ -499,14 +570,13 @@ test "countSegments: delimiter interspered" {
// Str.countGraphemeClusters
const grapheme = @import("helpers/grapheme.zig");
pub fn countGraphemeClusters(string: RocStr) callconv(.C) usize {
if (string.is_empty()) {
if (string.isEmpty()) {
return 0;
}
const bytes_len = string.len();
const bytes_ptr = string.as_u8_ptr();
const bytes_ptr = string.asU8ptr();
var bytes = bytes_ptr[0..bytes_len];
var iter = (unicode.Utf8View.init(bytes) catch unreachable).iterator();
@ -535,7 +605,7 @@ pub fn countGraphemeClusters(string: RocStr) callconv(.C) usize {
return count;
}
fn roc_str_from_literal(bytes_arr: *const []u8) RocStr {}
fn rocStrFromLiteral(bytes_arr: *const []u8) RocStr {}
test "countGraphemeClusters: empty string" {
const count = countGraphemeClusters(RocStr.empty());
@ -545,46 +615,60 @@ test "countGraphemeClusters: empty string" {
test "countGraphemeClusters: ascii characters" {
const bytes_arr = "abcd";
const bytes_len = bytes_arr.len;
const count = countGraphemeClusters(RocStr.init(bytes_arr, bytes_len));
const str = RocStr.init(testing.allocator, bytes_arr, bytes_len);
defer str.deinit(testing.allocator);
const count = countGraphemeClusters(str);
expectEqual(count, 4);
}
test "countGraphemeClusters: utf8 characters" {
const bytes_arr = "ãxā";
const bytes_len = bytes_arr.len;
const count = countGraphemeClusters(RocStr.init(bytes_arr, bytes_len));
const str = RocStr.init(testing.allocator, bytes_arr, bytes_len);
defer str.deinit(testing.allocator);
const count = countGraphemeClusters(str);
expectEqual(count, 3);
}
test "countGraphemeClusters: emojis" {
const bytes_arr = "🤔🤔🤔";
const bytes_len = bytes_arr.len;
const count = countGraphemeClusters(RocStr.init(bytes_arr, bytes_len));
const str = RocStr.init(testing.allocator, bytes_arr, bytes_len);
defer str.deinit(testing.allocator);
const count = countGraphemeClusters(str);
expectEqual(count, 3);
}
test "countGraphemeClusters: emojis and ut8 characters" {
const bytes_arr = "🤔å🤔¥🤔ç";
const bytes_len = bytes_arr.len;
const count = countGraphemeClusters(RocStr.init(bytes_arr, bytes_len));
const str = RocStr.init(testing.allocator, bytes_arr, bytes_len);
defer str.deinit(testing.allocator);
const count = countGraphemeClusters(str);
expectEqual(count, 6);
}
test "countGraphemeClusters: emojis, ut8, and ascii characters" {
const bytes_arr = "6🤔å🤔e¥🤔çpp";
const bytes_len = bytes_arr.len;
const count = countGraphemeClusters(RocStr.init(bytes_arr, bytes_len));
const str = RocStr.init(testing.allocator, bytes_arr, bytes_len);
defer str.deinit(testing.allocator);
const count = countGraphemeClusters(str);
expectEqual(count, 10);
}
// Str.startsWith
pub fn startsWith(string: RocStr, prefix: RocStr) callconv(.C) bool {
const bytes_len = string.len();
const bytes_ptr = string.as_u8_ptr();
const bytes_ptr = string.asU8ptr();
const prefix_len = prefix.len();
const prefix_ptr = prefix.as_u8_ptr();
const prefix_ptr = prefix.asU8ptr();
if (prefix_len > bytes_len) {
return false;
@ -602,31 +686,33 @@ pub fn startsWith(string: RocStr, prefix: RocStr) callconv(.C) bool {
}
test "startsWith: foo starts with fo" {
const foo = RocStr.init("foo", 3);
const fo = RocStr.init("fo", 2);
const foo = RocStr.init(testing.allocator, "foo", 3);
const fo = RocStr.init(testing.allocator, "fo", 2);
expect(startsWith(foo, fo));
}
test "startsWith: 123456789123456789 starts with 123456789123456789" {
const str = RocStr.init("123456789123456789", 18);
const str = RocStr.init(testing.allocator, "123456789123456789", 18);
defer str.deinit(testing.allocator);
expect(startsWith(str, str));
}
test "startsWith: 12345678912345678910 starts with 123456789123456789" {
const str = RocStr.init("12345678912345678910", 20);
const prefix = RocStr.init("123456789123456789", 18);
const str = RocStr.init(testing.allocator, "12345678912345678910", 20);
defer str.deinit(testing.allocator);
const prefix = RocStr.init(testing.allocator, "123456789123456789", 18);
defer prefix.deinit(testing.allocator);
expect(startsWith(str, prefix));
}
// Str.endsWith
pub fn endsWith(string: RocStr, suffix: RocStr) callconv(.C) bool {
const bytes_len = string.len();
const bytes_ptr = string.as_u8_ptr();
const bytes_ptr = string.asU8ptr();
const suffix_len = suffix.len();
const suffix_ptr = suffix.as_u8_ptr();
const suffix_ptr = suffix.asU8ptr();
if (suffix_len > bytes_len) {
return false;
@ -644,71 +730,57 @@ pub fn endsWith(string: RocStr, suffix: RocStr) callconv(.C) bool {
}
test "endsWith: foo ends with oo" {
const foo = RocStr.init("foo", 3);
const oo = RocStr.init("oo", 2);
const foo = RocStr.init(testing.allocator, "foo", 3);
const oo = RocStr.init(testing.allocator, "oo", 2);
defer foo.deinit(testing.allocator);
defer oo.deinit(testing.allocator);
expect(endsWith(foo, oo));
}
test "endsWith: 123456789123456789 ends with 123456789123456789" {
const str = RocStr.init("123456789123456789", 18);
const str = RocStr.init(testing.allocator, "123456789123456789", 18);
defer str.deinit(testing.allocator);
expect(endsWith(str, str));
}
test "endsWith: 12345678912345678910 ends with 345678912345678910" {
const str = RocStr.init("12345678912345678910", 20);
const suffix = RocStr.init("345678912345678910", 18);
const str = RocStr.init(testing.allocator, "12345678912345678910", 20);
const suffix = RocStr.init(testing.allocator, "345678912345678910", 18);
defer str.deinit(testing.allocator);
defer suffix.deinit(testing.allocator);
expect(endsWith(str, suffix));
}
test "endsWith: hello world ends with world" {
const str = RocStr.init("hello world", 11);
const suffix = RocStr.init("world", 5);
const str = RocStr.init(testing.allocator, "hello world", 11);
const suffix = RocStr.init(testing.allocator, "world", 5);
defer str.deinit(testing.allocator);
defer suffix.deinit(testing.allocator);
expect(endsWith(str, suffix));
}
// Str.concat
test "RocStr.concat: small concat small" {
const str1_len = 3;
var str1: [str1_len]u8 = "foo".*;
const str1_ptr: [*]u8 = &str1;
var roc_str1 = RocStr.init(str1_ptr, str1_len);
const str2_len = 3;
var str2: [str2_len]u8 = "abc".*;
const str2_ptr: [*]u8 = &str2;
var roc_str2 = RocStr.init(str2_ptr, str2_len);
const str3_len = 6;
var str3: [str3_len]u8 = "fooabc".*;
const str3_ptr: [*]u8 = &str3;
var roc_str3 = RocStr.init(str3_ptr, str3_len);
const result = strConcat(8, InPlace.Clone, roc_str1, roc_str2);
expect(roc_str3.eq(result));
roc_str1.drop();
roc_str2.drop();
roc_str3.drop();
result.drop();
// When we actually use this in Roc, libc will be linked so we have access to std.heap.c_allocator
pub fn strConcatC(ptr_size: u32, result_in_place: InPlace, arg1: RocStr, arg2: RocStr) callconv(.C) RocStr {
return strConcat(std.heap.c_allocator, ptr_size, result_in_place, arg1, arg2);
}
pub fn strConcat(ptr_size: u32, result_in_place: InPlace, arg1: RocStr, arg2: RocStr) callconv(.C) RocStr {
inline fn strConcat(allocator: *Allocator, ptr_size: u32, result_in_place: InPlace, arg1: RocStr, arg2: RocStr) RocStr {
return switch (ptr_size) {
4 => strConcatHelp(i32, result_in_place, arg1, arg2),
8 => strConcatHelp(i64, result_in_place, arg1, arg2),
4 => strConcatHelp(allocator, i32, result_in_place, arg1, arg2),
8 => strConcatHelp(allocator, i64, result_in_place, arg1, arg2),
else => unreachable,
};
}
fn strConcatHelp(comptime T: type, result_in_place: InPlace, arg1: RocStr, arg2: RocStr) RocStr {
if (arg1.is_empty()) {
return cloneStr(T, result_in_place, arg2);
} else if (arg2.is_empty()) {
return cloneStr(T, result_in_place, arg1);
fn strConcatHelp(allocator: *Allocator, comptime T: type, result_in_place: InPlace, arg1: RocStr, arg2: RocStr) RocStr {
if (arg1.isEmpty()) {
return cloneStr(allocator, T, result_in_place, arg2);
} else if (arg2.isEmpty()) {
return cloneStr(allocator, T, result_in_place, arg1);
} else {
const combined_length = arg1.len() + arg2.len();
@ -716,12 +788,12 @@ fn strConcatHelp(comptime T: type, result_in_place: InPlace, arg1: RocStr, arg2:
const result_is_big = combined_length >= small_str_bytes;
if (result_is_big) {
var result = allocate_str(T, result_in_place, combined_length);
var result = allocateStr(allocator, T, result_in_place, combined_length);
{
const old_if_small = &@bitCast([16]u8, arg1);
const old_if_big = @ptrCast([*]u8, arg1.str_bytes);
const old_bytes = if (arg1.is_small_str()) old_if_small else old_if_big;
const old_bytes = if (arg1.isSmallStr()) old_if_small else old_if_big;
const new_bytes: [*]u8 = @ptrCast([*]u8, result.str_bytes);
@ -731,7 +803,7 @@ fn strConcatHelp(comptime T: type, result_in_place: InPlace, arg1: RocStr, arg2:
{
const old_if_small = &@bitCast([16]u8, arg2);
const old_if_big = @ptrCast([*]u8, arg2.str_bytes);
const old_bytes = if (arg2.is_small_str()) old_if_small else old_if_big;
const old_bytes = if (arg2.isSmallStr()) old_if_small else old_if_big;
const new_bytes = @ptrCast([*]u8, result.str_bytes) + arg1.len();
@ -775,12 +847,12 @@ const InPlace = packed enum(u8) {
Clone,
};
fn cloneStr(comptime T: type, in_place: InPlace, str: RocStr) RocStr {
if (str.is_small_str() or str.is_empty()) {
fn cloneStr(allocator: *Allocator, comptime T: type, in_place: InPlace, str: RocStr) RocStr {
if (str.isSmallStr() or str.isEmpty()) {
// just return the bytes
return str;
} else {
var new_str = allocate_str(T, in_place, str.str_len);
var new_str = allocateStr(allocator, T, in_place, str.str_len);
var old_bytes: [*]u8 = @ptrCast([*]u8, str.str_bytes);
var new_bytes: [*]u8 = @ptrCast([*]u8, new_str.str_bytes);
@ -791,9 +863,9 @@ fn cloneStr(comptime T: type, in_place: InPlace, str: RocStr) RocStr {
}
}
fn allocate_str(comptime T: type, in_place: InPlace, number_of_chars: u64) RocStr {
fn allocateStr(allocator: *Allocator, comptime T: type, in_place: InPlace, number_of_chars: u64) RocStr {
const length = @sizeOf(T) + number_of_chars;
var new_bytes: [*]T = @ptrCast([*]T, @alignCast(@alignOf(T), malloc(length)));
var new_bytes: []T = allocator.alloc(T, length) catch unreachable;
if (in_place == InPlace.InPlace) {
new_bytes[0] = @intCast(T, number_of_chars);
@ -802,10 +874,39 @@ fn allocate_str(comptime T: type, in_place: InPlace, number_of_chars: u64) RocSt
}
var first_element = @ptrCast([*]align(@alignOf(T)) u8, new_bytes);
first_element += 8;
first_element += @sizeOf(usize);
return RocStr{
.str_bytes = first_element,
.str_len = number_of_chars,
};
}
test "RocStr.concat: small concat small" {
const str1_len = 3;
var str1: [str1_len]u8 = "foo".*;
const str1_ptr: [*]u8 = &str1;
var roc_str1 = RocStr.init(testing.allocator, str1_ptr, str1_len);
const str2_len = 3;
var str2: [str2_len]u8 = "abc".*;
const str2_ptr: [*]u8 = &str2;
var roc_str2 = RocStr.init(testing.allocator, str2_ptr, str2_len);
const str3_len = 6;
var str3: [str3_len]u8 = "fooabc".*;
const str3_ptr: [*]u8 = &str3;
var roc_str3 = RocStr.init(testing.allocator, str3_ptr, str3_len);
defer {
roc_str1.deinit(testing.allocator);
roc_str2.deinit(testing.allocator);
roc_str3.deinit(testing.allocator);
}
const result = strConcat(testing.allocator, 8, InPlace.Clone, roc_str1, roc_str2);
defer result.deinit(testing.allocator);
expect(roc_str3.eq(result));
}

View file

@ -1,6 +1,8 @@
use std::convert::AsRef;
use std::env;
use std::ffi::OsStr;
use std::fs::{self};
use std::io;
use std::path::Path;
use std::process::Command;
use std::str;
@ -11,65 +13,104 @@ fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
// "." is relative to where "build.rs" is
let src_path = Path::new(".").join("bitcode").join("src");
let main_zig_path = src_path.join("main.zig");
let build_script_dir_path = Path::new(".");
let src_path_str = src_path.to_str().expect("Invalid src path");
println!("Building main.zig from: {}", src_path_str);
let bitcode_path = build_script_dir_path.join("bitcode");
let zig_cache_dir = Path::new(&out_dir).join("zig-cache");
let zig_cache_dir_str = zig_cache_dir.to_str().expect("Invalid zig cache dir");
println!("Setting zig cache to: {}", zig_cache_dir_str);
let src_path = bitcode_path.join("src");
let dest_ll_path = Path::new(&out_dir).join("builtins.ll");
let dest_ll = dest_ll_path.to_str().expect("Invalid dest ir path");
let emit_ir_arg = "-femit-llvm-ir=".to_owned() + dest_ll;
println!("Compiling zig llvm-ir to: {}", dest_ll);
let build_zig_path = bitcode_path.join("build.zig");
let build_zig = build_zig_path.to_str().expect("Invalid build path");
let dest_ir_path = build_script_dir_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(
"zig",
&[
"build-obj",
main_zig_path.to_str().unwrap(),
&emit_ir_arg,
"-fno-emit-bin",
"--strip",
"-O",
"ReleaseFast",
"--cache-dir",
zig_cache_dir_str,
],
&["build", "ir", "-Drelease=true", "--build-file", build_zig],
);
let dest_bc_path = Path::new(&out_dir).join("builtins.bc");
let dest_bc = dest_bc_path.to_str().expect("Invalid dest bc path");
println!("Compiling bitcode to: {}", dest_bc);
run_command("llvm-as-10", &[dest_ll, "-o", dest_bc]);
run_command("llvm-as-10", &[dest_ir, "-o", dest_bc]);
// TODO: Recursivly search zig src dir to watch for each file
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed={}", src_path_str);
println!("cargo:rustc-env=BUILTINS_BC={}", dest_bc);
get_zig_files(src_path.as_path(), &|path| {
let path: &Path = path;
println!(
"cargo:rerun-if-changed={}",
path.to_str().expect("Failed to convert path to str")
);
})
.unwrap();
}
fn run_command<S, I>(command: &str, args: I)
fn run_command<S, I>(command_str: &str, args: I)
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let output_result = Command::new(OsStr::new(&command)).args(args).output();
let output_result = Command::new(OsStr::new(&command_str)).args(args).output();
match output_result {
Ok(output) => match output.status.success() {
true => (),
false => {
let error_str = match str::from_utf8(&output.stderr) {
Ok(stderr) => stderr.to_string(),
Err(_) => format!("Failed to run \"{}\"", command),
Err(_) => format!("Failed to run \"{}\"", command_str),
};
panic!("{} failed: {}", command, error_str);
panic!("{} failed: {}", command_str, error_str);
}
},
Err(reason) => panic!("{} failed: {}", command, reason),
Err(reason) => panic!("{} failed: {}", command_str, reason),
}
}
fn get_zig_files(dir: &Path, cb: &dyn Fn(&Path)) -> io::Result<()> {
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path_buf = entry.path();
if path_buf.is_dir() {
get_zig_files(&path_buf, cb).unwrap();
} else {
let path = path_buf.as_path();
match path.extension() {
Some(osstr) if osstr == "zig" => {
cb(path);
}
_ => {}
}
}
}
}
Ok(())
}
// fn get_zig_files(dir: &Path) -> io::Result<Vec<&Path>> {
// let mut vec = Vec::new();
// if dir.is_dir() {
// for entry in fs::read_dir(dir)? {
// let entry = entry?;
// let path_buf = entry.path();
// if path_buf.is_dir() {
// match get_zig_files(&path_buf) {
// Ok(sub_files) => vec = [vec, sub_files].concat(),
// Err(_) => (),
// };
// } else {
// let path = path_buf.as_path();
// let path_ext = path.extension().unwrap();
// if path_ext == "zig" {
// vec.push(path.clone());
// }
// }
// }
// }
// Ok(vec)
// }

View file

@ -1207,7 +1207,7 @@ fn int_type(u: VarId) -> SolvedType {
vec![
flex(u),
SolvedType::Alias(
Symbol::NUM_INT,
Symbol::NUM_I64,
Vec::new(),
Box::new(builtin_aliases::num_type(SolvedType::Apply(
Symbol::ATTR_ATTR,

View file

@ -172,9 +172,9 @@ pub struct Field {
#[derive(Clone, Debug, PartialEq)]
pub enum Recursive {
Recursive,
TailRecursive,
NotRecursive,
NotRecursive = 0,
Recursive = 1,
TailRecursive = 2,
}
#[derive(Clone, Debug, PartialEq)]

View file

@ -97,6 +97,7 @@ pub fn canonicalize_module_defs<'a>(
let first_char = ident.as_inline_str().chars().next().unwrap();
if first_char.is_lowercase() {
// this is a value definition
let expr_var = var_store.fresh();
match scope.import(ident, symbol, region) {
@ -116,7 +117,10 @@ pub fn canonicalize_module_defs<'a>(
}
}
} else {
panic!("TODO add type aliases to type alias dictionary, based on exposed types");
// This is a type alias
// the should already be added to the scope when this module is canonicalized
debug_assert!(scope.contains_alias(symbol));
}
}

View file

@ -192,4 +192,8 @@ impl Scope {
self.aliases.insert(name, alias);
}
pub fn contains_alias(&mut self, name: Symbol) -> bool {
self.aliases.contains_key(&name)
}
}

View file

@ -50,7 +50,7 @@ mod test_can {
assert_eq!(expected, actual);
}
actual => {
panic!("Expected an Int, but got: {:?}", actual);
panic!("Expected an I64, but got: {:?}", actual);
}
}
}
@ -249,7 +249,7 @@ mod test_can {
fn correct_annotated_body() {
let src = indoc!(
r#"
f : Int -> Int
f : I64 -> I64
f = \ a -> a
f
@ -265,7 +265,7 @@ mod test_can {
fn correct_annotated_body_with_comments() {
let src = indoc!(
r#"
f : Int -> Int # comment
f : I64 -> I64 # comment
f = \ a -> a
f
@ -281,7 +281,7 @@ mod test_can {
fn name_mismatch_annotated_body() {
let src = indoc!(
r#"
f : Int -> Int
f : I64 -> I64
g = \ a -> a
g
@ -307,7 +307,7 @@ mod test_can {
fn name_mismatch_annotated_body_with_comment() {
let src = indoc!(
r#"
f : Int -> Int # comment
f : I64 -> I64 # comment
g = \ a -> a
g
@ -333,7 +333,7 @@ mod test_can {
fn separated_annotated_body() {
let src = indoc!(
r#"
f : Int -> Int
f : I64 -> I64
f = \ a -> a
@ -354,7 +354,7 @@ mod test_can {
fn separated_annotated_body_with_comment() {
let src = indoc!(
r#"
f : Int -> Int
f : I64 -> I64
# comment
f = \ a -> a
@ -375,9 +375,9 @@ mod test_can {
fn shadowed_annotation() {
let src = indoc!(
r#"
f : Int -> Int
f : I64 -> I64
f : Int -> Int
f : I64 -> I64
f
"#
@ -397,7 +397,7 @@ mod test_can {
fn correct_nested_unannotated_body() {
let src = indoc!(
r#"
f : Int
f : I64
f =
g = 42
@ -416,9 +416,9 @@ mod test_can {
fn correct_nested_annotated_body() {
let src = indoc!(
r#"
f : Int
f : I64
f =
g : Int
g : I64
g = 42
g + 1
@ -436,11 +436,11 @@ mod test_can {
fn correct_nested_body_annotated_multiple_lines() {
let src = indoc!(
r#"
f : Int
f : I64
f =
g : Int
g : I64
g = 42
h : Int
h : I64
h = 5
z = 4
g + h + z
@ -458,10 +458,10 @@ mod test_can {
fn correct_nested_body_unannotated_multiple_lines() {
let src = indoc!(
r#"
f : Int
f : I64
f =
g = 42
h : Int
h : I64
h = 5
z = 4
g + h + z
@ -478,7 +478,7 @@ mod test_can {
fn correct_double_nested_body() {
let src = indoc!(
r#"
f : Int
f : I64
f =
g =
h = 42
@ -499,7 +499,7 @@ mod test_can {
fn annotation_followed_with_unrelated_affectation() {
let src = indoc!(
r#"
F : Int
F : I64
x = 1
@ -520,9 +520,9 @@ mod test_can {
fn two_annotations_followed_with_unrelated_affectation() {
let src = indoc!(
r#"
G : Int
G : I64
F : Int
F : I64
x = 1

View file

@ -92,7 +92,7 @@ pub fn num_floatingpoint() -> Type {
#[inline(always)]
pub fn num_int() -> Type {
Type::Alias(Symbol::NUM_INT, vec![], Box::new(num_num(num_integer())))
Type::Alias(Symbol::NUM_I64, vec![], Box::new(num_num(num_integer())))
}
#[inline(always)]

View file

@ -762,8 +762,8 @@ mod test_fmt {
expr_formats_to(
indoc!(
r#"
f: { y : Int,
x : Int ,
f: { y : I64,
x : I64 ,
}
f"#
@ -772,8 +772,8 @@ mod test_fmt {
r#"
f :
{
y : Int,
x : Int,
y : I64,
x : I64,
}
f"#
@ -787,8 +787,8 @@ mod test_fmt {
r#"
f :
{
y : Int,
x : Int,
y : I64,
x : I64,
}
f"#
@ -800,7 +800,7 @@ mod test_fmt {
expr_formats_same(indoc!(
r#"
f :
Int
I64
f"#
));
@ -880,7 +880,7 @@ mod test_fmt {
r#"
f :
{
x: Int # comment 1
x: I64 # comment 1
,
# comment 2
}
@ -891,7 +891,7 @@ mod test_fmt {
r#"
f :
{
x : Int,
x : I64,
# comment 1
# comment 2
}
@ -2458,7 +2458,7 @@ mod test_fmt {
fn record_type() {
expr_formats_same(indoc!(
r#"
f : { foo : Int }
f : { foo : I64 }
f = { foo: 1000 }
a
@ -2509,11 +2509,11 @@ mod test_fmt {
// r#"
// f :
// Result a
// { x : Int
// { x : I64
// , y : Float
// }
// c
// -> Int
// -> I64
// f =
// \_ -> 4
// "#

View file

@ -1230,7 +1230,20 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
use roc_mono::ir::Stmt::*;
match stmt {
Let(symbol, expr, layout, cont) => {
Let(first_symbol, first_expr, first_layout, mut cont) => {
let mut queue = Vec::new_in(env.arena);
queue.push((first_symbol, first_expr, first_layout));
while let Let(symbol, expr, layout, new_cont) = cont {
queue.push((symbol, expr, layout));
cont = new_cont;
}
let mut stack = Vec::with_capacity_in(queue.len(), env.arena);
for (symbol, expr, layout) in queue {
let context = &env.context;
let val = build_exp_expr(env, layout_ids, &scope, parent, layout, &expr);
@ -1248,8 +1261,12 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
} else {
basic_type_from_layout(env.arena, context, &layout, env.ptr_bytes)
};
let alloca =
create_entry_block_alloca(env, parent, expr_bt, symbol.ident_string(&env.interns));
let alloca = create_entry_block_alloca(
env,
parent,
expr_bt,
symbol.ident_string(&env.interns),
);
env.builder.build_store(alloca, val);
@ -1261,8 +1278,14 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
// scope = scope.clone();
scope.insert(*symbol, (layout.clone(), alloca));
stack.push(*symbol);
}
let result = build_exp_stmt(env, layout_ids, scope, parent, cont);
scope.remove(symbol);
for symbol in stack {
scope.remove(&symbol);
}
result
}

View file

@ -49,12 +49,7 @@ pub fn str_split<'a, 'ctx, 'env>(
call_void_bitcode_fn(
env,
&[
ret_list_ptr_zig_rocstr,
BasicValueEnum::IntValue(segment_count),
str_i128.into(),
delim_i128.into(),
],
&[ret_list_ptr_zig_rocstr, str_i128.into(), delim_i128.into()],
&bitcode::STR_STR_SPLIT_IN_PLACE,
);

View file

@ -162,7 +162,7 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
initThrees : List Int
initThrees : List I64
initThrees =
[]
@ -204,7 +204,7 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
init : List Int
init : List I64
init =
[]
@ -251,7 +251,7 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
empty : List Int
empty : List I64
empty =
[]
@ -330,7 +330,7 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
empty : List Int
empty : List I64
empty =
[]
@ -347,7 +347,7 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
alwaysTrue : Int -> Bool
alwaysTrue : I64 -> Bool
alwaysTrue = \_ ->
True
@ -365,11 +365,11 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
alwaysTrue : Int -> Bool
alwaysTrue : I64 -> Bool
alwaysTrue = \_ ->
True
oneThroughEight : List Int
oneThroughEight : List I64
oneThroughEight =
[1,2,3,4,5,6,7,8]
@ -386,7 +386,7 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
alwaysFalse : Int -> Bool
alwaysFalse : I64 -> Bool
alwaysFalse = \_ ->
False
@ -403,7 +403,7 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
intIsLessThanThree : Int -> Bool
intIsLessThanThree : I64 -> Bool
intIsLessThanThree = \i ->
i < 3
@ -440,7 +440,7 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
empty : List Int
empty : List I64
empty =
[]
@ -457,7 +457,7 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
nonEmpty : List Int
nonEmpty : List I64
nonEmpty =
[ 1 ]
@ -474,7 +474,7 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
nonEmpty : List Int
nonEmpty : List I64
nonEmpty =
[ 1 ]
@ -491,7 +491,7 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
nonEmpty : List Int
nonEmpty : List I64
nonEmpty =
[ 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5 ]
@ -510,7 +510,7 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
nonEmpty : List Int
nonEmpty : List I64
nonEmpty =
[ 1, 1, -4, 1, 2 ]
@ -528,11 +528,11 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
nonEmpty : List Int
nonEmpty : List I64
nonEmpty =
[ 2, 2, -4, 2, 3 ]
greaterThanOne : Int -> Bool
greaterThanOne : I64 -> Bool
greaterThanOne = \i ->
i > 1
@ -730,7 +730,7 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
emptyList : List Int
emptyList : List I64
emptyList =
[]
@ -752,11 +752,11 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
firstList : List Int
firstList : List I64
firstList =
[]
secondList : List Int
secondList : List I64
secondList =
[]
@ -778,11 +778,11 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
firstList : List Int
firstList : List I64
firstList =
[]
secondList : List Int
secondList : List I64
secondList =
[]
@ -1229,7 +1229,7 @@ mod gen_list {
app "quicksort" provides [ main ] to "./platform"
swap : Int, Int, List a -> List a
swap : I64, I64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -1254,7 +1254,7 @@ mod gen_list {
// assert_evals_to!(
// indoc!(
// r#"
// swap : Int, Int, List a -> List a
// swap : I64, I64, List a -> List a
// swap = \i, j, list ->
// when Pair (List.get list i) (List.get list j) is
// Pair (Ok atI) (Ok atJ) ->
@ -1264,7 +1264,7 @@ mod gen_list {
//
// _ ->
// []
// partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
// partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ]
// partition = \low, high, initialList ->
// when List.get initialList high is
// Ok pivot ->
@ -1276,7 +1276,7 @@ mod gen_list {
// Pair (low - 1) initialList
//
//
// partitionHelp : Int, Int, List (Num a), Int, Int -> [ Pair Int (List (Num a)) ]
// partitionHelp : I64, I64, List (Num a), I64, I64 -> [ Pair I64 (List (Num a)) ]
// partitionHelp = \i, j, list, high, pivot ->
// if j < high then
// when List.get list j is
@ -1306,7 +1306,7 @@ mod gen_list {
// assert_evals_to!(
// indoc!(
// r#"
// swap : Int, Int, List a -> List a
// swap : I64, I64, List a -> List a
// swap = \i, j, list ->
// when Pair (List.get list i) (List.get list j) is
// Pair (Ok atI) (Ok atJ) ->
@ -1316,7 +1316,7 @@ mod gen_list {
//
// _ ->
// []
// partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
// partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ]
// partition = \low, high, initialList ->
// when List.get initialList high is
// Ok pivot ->
@ -1328,7 +1328,7 @@ mod gen_list {
// Pair (low - 1) initialList
//
//
// partitionHelp : Int, Int, List (Num a), Int, Int -> [ Pair Int (List (Num a)) ]
// partitionHelp : I64, I64, List (Num a), I64, I64 -> [ Pair I64 (List (Num a)) ]
//
// # when partition 0 0 [ 1,2,3,4,5 ] is
// # Pair list _ -> list
@ -1352,7 +1352,7 @@ mod gen_list {
quicksortHelp list 0 (n - 1)
quicksortHelp : List (Num a), Int, Int -> List (Num a)
quicksortHelp : List (Num a), I64, I64 -> List (Num a)
quicksortHelp = \list, low, high ->
if low < high then
when partition low high list is
@ -1364,7 +1364,7 @@ mod gen_list {
list
swap : Int, Int, List a -> List a
swap : I64, I64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -1375,7 +1375,7 @@ mod gen_list {
_ ->
[]
partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -1387,7 +1387,7 @@ mod gen_list {
Pair (low - 1) initialList
partitionHelp : Int, Int, List (Num a), Int, (Num a) -> [ Pair Int (List (Num a)) ]
partitionHelp : I64, I64, List (Num a), I64, (Num a) -> [ Pair I64 (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is
@ -1422,7 +1422,7 @@ mod gen_list {
quicksortHelp list 0 (List.len list - 1)
quicksortHelp : List (Num a), Int, Int -> List (Num a)
quicksortHelp : List (Num a), I64, I64 -> List (Num a)
quicksortHelp = \list, low, high ->
if low < high then
when partition low high list is
@ -1434,7 +1434,7 @@ mod gen_list {
list
swap : Int, Int, List a -> List a
swap : I64, I64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -1445,7 +1445,7 @@ mod gen_list {
_ ->
[]
partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -1457,7 +1457,7 @@ mod gen_list {
Pair (low - 1) initialList
partitionHelp : Int, Int, List (Num a), Int, Num a -> [ Pair Int (List (Num a)) ]
partitionHelp : I64, I64, List (Num a), I64, Num a -> [ Pair I64 (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot ->
# if j < high then
if False then
@ -1495,7 +1495,7 @@ mod gen_list {
quicksortHelp list 0 (List.len list - 1)
quicksortHelp : List (Num a), Int, Int -> List (Num a)
quicksortHelp : List (Num a), I64, I64 -> List (Num a)
quicksortHelp = \list, low, high ->
if low < high then
when partition low high list is
@ -1507,7 +1507,7 @@ mod gen_list {
list
swap : Int, Int, List a -> List a
swap : I64, I64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -1518,7 +1518,7 @@ mod gen_list {
_ ->
[]
partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -1530,7 +1530,7 @@ mod gen_list {
Pair (low - 1) initialList
partitionHelp : Int, Int, List (Num a), Int, Num a -> [ Pair Int (List (Num a)) ]
partitionHelp : I64, I64, List (Num a), I64, Num a -> [ Pair I64 (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is
@ -1562,7 +1562,7 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
x : List Int
x : List I64
x = []
List.len x + List.len x
@ -1578,7 +1578,7 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
x : List Int
x : List I64
x = [1,2,3]
List.len x + List.len x
@ -1594,10 +1594,10 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
x : List Int
x : List I64
x = [1,2,3]
id : List Int -> List Int
id : List I64 -> List I64
id = \y -> y
id x
@ -1613,10 +1613,10 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
x : List Int
x : List I64
x = [1,2,3]
id : List Int -> List Int
id : List I64 -> List I64
id = \y -> List.set y 0 0
id x
@ -1632,7 +1632,7 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
id : List Int -> [ Pair (List Int) Int ]
id : List I64 -> [ Pair (List I64) I64 ]
id = \y -> Pair y 4
when id [1,2,3] is

View file

@ -135,7 +135,7 @@ mod gen_primitives {
assert_evals_to!(
indoc!(
r#"
x : [ Pair Int Int ]
x : [ Pair I64 I64 ]
x = Pair 0x2 0x3
when x is
@ -152,7 +152,7 @@ mod gen_primitives {
assert_evals_to!(
indoc!(
r#"
x : [A Int, B Int]
x : [A I64, B I64]
x = A 0x2
when x is
@ -170,7 +170,7 @@ mod gen_primitives {
assert_evals_to!(
indoc!(
r#"
x : [A Int, B Int]
x : [A I64, B I64]
x = B 0x3
when x is
@ -293,7 +293,7 @@ mod gen_primitives {
indoc!(
r#"
wrapper = \{} ->
alwaysFloatIdentity : Int -> (F64 -> F64)
alwaysFloatIdentity : I64 -> (F64 -> F64)
alwaysFloatIdentity = \_ ->
(\a -> a)
@ -557,14 +557,14 @@ mod gen_primitives {
LinkedList a : [ Nil, Cons a (LinkedList a) ]
len : LinkedList a -> Int
len : LinkedList a -> I64
len = \list ->
when list is
Nil -> 0
Cons _ rest -> 1 + len rest
main =
nil : LinkedList Int
nil : LinkedList I64
nil = Nil
len nil
@ -584,10 +584,10 @@ mod gen_primitives {
LinkedList a : [ Nil, Cons a (LinkedList a) ]
nil : LinkedList Int
nil : LinkedList I64
nil = Nil
length : LinkedList a -> Int
length : LinkedList a -> I64
length = \list ->
when list is
Nil -> 0
@ -611,10 +611,10 @@ mod gen_primitives {
LinkedList a : [ Nil, Cons a (LinkedList a) ]
one : LinkedList Int
one : LinkedList I64
one = Cons 1 Nil
length : LinkedList a -> Int
length : LinkedList a -> I64
length = \list ->
when list is
Nil -> 0
@ -638,10 +638,10 @@ mod gen_primitives {
LinkedList a : [ Nil, Cons a (LinkedList a) ]
one : LinkedList Int
one : LinkedList I64
one = Cons 1 Nil
length : LinkedList a -> Int
length : LinkedList a -> I64
length = \list ->
when list is
Nil -> 0
@ -665,10 +665,10 @@ mod gen_primitives {
LinkedList a : [ Nil, Cons a (LinkedList a) ]
three : LinkedList Int
three : LinkedList I64
three = Cons 3 (Cons 2 (Cons 1 Nil))
length : LinkedList a -> Int
length : LinkedList a -> I64
length = \list ->
when list is
Nil -> 0
@ -693,7 +693,7 @@ mod gen_primitives {
LinkedList a : [ Nil, Cons a (LinkedList a) ]
three : LinkedList Int
three : LinkedList I64
three = Cons 3 (Cons 2 (Cons 1 Nil))
@ -721,10 +721,10 @@ mod gen_primitives {
LinkedList a : [ Nil, Cons a (LinkedList a) ]
zero : LinkedList Int
zero : LinkedList I64
zero = Nil
sum : LinkedList Int -> Int
sum : LinkedList I64 -> I64
sum = \list ->
when list is
Nil -> 0
@ -748,7 +748,7 @@ mod gen_primitives {
LinkedList a : [ Nil, Cons a (LinkedList a) ]
three : LinkedList Int
three : LinkedList I64
three = Cons 3 (Cons 2 (Cons 1 Nil))
sum : LinkedList (Num a) -> Num a
@ -779,7 +779,7 @@ mod gen_primitives {
r#"
Maybe a : [ Nothing, Just a ]
x : Maybe (Maybe Int)
x : Maybe (Maybe I64)
x = Just (Just 41)
when x is
@ -796,7 +796,7 @@ mod gen_primitives {
r#"
Maybe a : [ Nothing, Just a ]
x : Maybe (Maybe Int)
x : Maybe (Maybe I64)
x = Just Nothing
when x is
@ -814,7 +814,7 @@ mod gen_primitives {
r#"
Maybe a : [ Nothing, Just a ]
x : Maybe (Maybe Int)
x : Maybe (Maybe I64)
x = Nothing
when x is
@ -1128,7 +1128,7 @@ mod gen_primitives {
main : Bool
main =
myList : ConsList Int
myList : ConsList I64
myList = empty
isEmpty myList
@ -1159,7 +1159,7 @@ mod gen_primitives {
main : Bool
main =
myList : ConsList Int
myList : ConsList I64
myList = Cons 0x1 Nil
isEmpty myList
@ -1177,16 +1177,16 @@ mod gen_primitives {
r#"
app "test" provides [ main ] to "./platform"
State a : { count : Int, x : a }
State a : { count : I64, x : a }
foo : State a -> Int
foo : State a -> I64
foo = \state ->
if state.count == 0 then
0
else
1 + foo { count: state.count - 1, x: state.x }
main : Int
main : I64
main =
foo { count: 3, x: {} }
"#
@ -1267,7 +1267,7 @@ mod gen_primitives {
_ ->
Node color key value left right
main : Dict Int {}
main : Dict I64 {}
main =
insert 0 {} Empty
"#
@ -1308,7 +1308,7 @@ mod gen_primitives {
_ ->
Empty
main : Dict Int
main : Dict I64
main =
balance Red 0 Empty Empty
"#
@ -1331,7 +1331,7 @@ mod gen_primitives {
balance = \key, left ->
Node key left Empty
main : Dict Int
main : Dict I64
main =
balance 0 Empty
"#
@ -1378,7 +1378,7 @@ mod gen_primitives {
_ ->
Empty
main : Dict Int Int
main : Dict I64 I64
main =
balance Red 0 0 Empty Empty
"#
@ -1428,7 +1428,7 @@ mod gen_primitives {
_ ->
Node color key value left right
main : Dict Int Int
main : Dict I64 I64
main =
balance Red 0 0 Empty Empty
"#
@ -1448,7 +1448,7 @@ mod gen_primitives {
ConsList a : [ Cons a (ConsList a), Nil ]
balance : ConsList Int -> Int
balance : ConsList I64 -> I64
balance = \right ->
when right is
Cons 1 foo ->
@ -1457,7 +1457,7 @@ mod gen_primitives {
_ -> 3
_ -> 3
main : Int
main : I64
main =
when balance Nil is
_ -> 3
@ -1474,13 +1474,13 @@ mod gen_primitives {
ConsList a : [ Cons a (ConsList a), Nil ]
balance : ConsList Int -> Int
balance : ConsList I64 -> I64
balance = \right ->
when right is
Cons 1 (Cons 1 _) -> 3
_ -> 3
main : Int
main : I64
main =
when balance Nil is
_ -> 3
@ -1502,7 +1502,7 @@ mod gen_primitives {
ConsList a : [ Cons a (ConsList a), Nil ]
balance : ConsList Int -> Int
balance : ConsList I64 -> I64
balance = \right ->
when right is
Cons 1 foo ->
@ -1511,7 +1511,7 @@ mod gen_primitives {
_ -> 3
_ -> 3
main : Int
main : I64
main =
when balance Nil is
_ -> 3
@ -1531,13 +1531,13 @@ mod gen_primitives {
ConsList a : [ Cons a (ConsList a), Nil ]
foo : ConsList Int -> Int
foo : ConsList I64 -> I64
foo = \list ->
when list is
Cons _ (Cons x _) -> x
_ -> 0
main : Int
main : I64
main =
foo (Cons 1 (Cons 32 Nil))
"#
@ -1554,15 +1554,15 @@ mod gen_primitives {
r#"
app "test" provides [ main ] to "./platform"
BTree : [ Node BTree BTree, Leaf Int ]
BTree : [ Node BTree BTree, Leaf I64 ]
foo : BTree -> Int
foo : BTree -> I64
foo = \btree ->
when btree is
Node (Node (Leaf x) _) _ -> x
_ -> 0
main : Int
main : I64
main =
foo (Node (Node (Leaf 32) (Leaf 0)) (Leaf 0))
"#

View file

@ -20,7 +20,7 @@ mod gen_tags {
r#"
Maybe a : [ Just a, Nothing ]
x : Maybe Int
x : Maybe I64
x = Nothing
x
@ -39,7 +39,7 @@ mod gen_tags {
r#"
Maybe a : [ Just a, Nothing ]
x : Maybe Int
x : Maybe I64
x = Nothing
x
@ -58,7 +58,7 @@ mod gen_tags {
r#"
Maybe a : [ Just a, Nothing ]
y : Maybe Int
y : Maybe I64
y = Just 0x4
y
@ -76,7 +76,7 @@ mod gen_tags {
r#"
Maybe a : [ Just a, Nothing ]
y : Maybe Int
y : Maybe I64
y = Just 0x4
y
@ -114,7 +114,7 @@ mod gen_tags {
// assert_evals_to!(
// indoc!(
// r#"
// x : Result Int Int
// x : Result I64 I64
// x = Err 41
// x
@ -185,7 +185,7 @@ mod gen_tags {
// r#"
// LinkedList a : [ Cons a (LinkedList a), Nil ]
//
// empty : LinkedList Int
// empty : LinkedList I64
// empty = Nil
//
// 1
@ -203,7 +203,7 @@ mod gen_tags {
// r#"
// LinkedList a : [ Cons a (LinkedList a), Nil ]
//
// singleton : LinkedList Int
// singleton : LinkedList I64
// singleton = Cons 0x1 Nil
//
// 1
@ -290,7 +290,7 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r#"
x : [ Nothing, Just Int ]
x : [ Nothing, Just I64 ]
x = Nothing
when x is
@ -308,7 +308,7 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r#"
x : [ Nothing, Just Int ]
x : [ Nothing, Just I64 ]
x = Just 41
when x is
@ -326,7 +326,7 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r#"
x : Result Int Int
x : Result I64 I64
x = Err 41
when x is
@ -346,7 +346,7 @@ mod gen_tags {
r#"
These a b : [ This a, That b, These a b ]
x : These Int Int
x : These I64 I64
x = These 0x3 0x2
when x is
@ -398,7 +398,7 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r#"
x : Result Int Int
x : Result I64 I64
x = Ok 2
when x is
@ -464,7 +464,7 @@ mod gen_tags {
r#"
Maybe a : [ Nothing, Just a ]
x : Maybe (Maybe Int)
x : Maybe (Maybe I64)
x = Just (Just 41)
when x is
@ -558,7 +558,7 @@ mod gen_tags {
r#"
Unit : [ Unit ]
f : Unit -> Int
f : Unit -> I64
f = \Unit -> 42
f Unit
@ -587,7 +587,7 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r#"
f : {} -> Int
f : {} -> I64
f = \{} -> 42
f {}
@ -614,7 +614,7 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r#"
x : [ Pair Int ]
x : [ Pair I64 ]
x = Pair 2
x
@ -634,7 +634,7 @@ mod gen_tags {
Maybe a : [ Nothing, Just a ]
x : Maybe (Maybe Int)
x : Maybe (Maybe I64)
x = Just (Just 41)
main =
@ -806,7 +806,7 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r"#
x : [ Three Bool Int, Empty ]
x : [ Three Bool I64, Empty ]
x = Three (1 == 1) 32
x
@ -820,7 +820,7 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r"#
x : [ Three Bool [ Red, Green, Blue ] Int, Empty ]
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
@ -836,7 +836,7 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r"#
x : [ Three Bool Int, Empty ]
x : [ Three Bool I64, Empty ]
x = Three (1 == 1) 32
when x is
@ -854,7 +854,7 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r"#
x : [ Three Bool [ Red, Green, Blue ] Int, Empty ]
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

View file

@ -1,5 +1,6 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;

View file

@ -75,7 +75,6 @@ pub fn helper<'a>(
println!("{:?}", exposed_to_host);
println!("=================================\n");
*/
debug_assert_eq!(exposed_to_host.len(), 1);
let main_fn_symbol = exposed_to_host.keys().copied().nth(0).unwrap();

View file

@ -13,14 +13,18 @@ use roc_constrain::module::{
constrain_imports, pre_constrain_imports, ConstrainableImports, Import,
};
use roc_constrain::module::{constrain_module, ExposedModuleTypes, SubsByModule};
use roc_module::ident::{Ident, Lowercase, ModuleName, TagName};
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_module::ident::{Ident, Lowercase, ModuleName, QualifiedModuleName, TagName};
use roc_module::symbol::{
IdentIds, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds, Symbol,
};
use roc_mono::ir::{
CapturedSymbols, ExternalSpecializations, PartialProc, PendingSpecialization, Proc, Procs,
};
use roc_mono::layout::{Layout, LayoutCache};
use roc_parse::ast::{self, Attempting, StrLiteral, TypeAnnotation};
use roc_parse::header::{ExposesEntry, ImportsEntry, PlatformHeader, TypedIdent};
use roc_parse::header::{
ExposesEntry, ImportsEntry, PackageEntry, PackageOrPath, PlatformHeader, To, TypedIdent,
};
use roc_parse::module::module_defs;
use roc_parse::parser::{self, Fail, Parser};
use roc_region::all::{Located, Region};
@ -101,6 +105,7 @@ impl Dependencies {
pub fn add_module(
&mut self,
module_id: ModuleId,
opt_effect_module: Option<ModuleId>,
dependencies: &MutSet<ModuleId>,
goal_phase: Phase,
) -> MutSet<(ModuleId, Phase)> {
@ -143,7 +148,8 @@ impl Dependencies {
// all the dependencies can be loaded
for dep in dependencies {
// TODO figure out how to "load" (because it doesn't exist on the file system) the Effect module
if !format!("{:?}", dep).contains("Effect") {
if Some(*dep) != opt_effect_module {
output.insert((*dep, LoadHeader));
}
}
@ -226,7 +232,13 @@ impl Dependencies {
self.add_dependency_help(a, b, phase, phase);
}
/// phase_a of module a is waiting for phase_b of module_b
fn add_dependency_help(&mut self, a: ModuleId, b: ModuleId, phase_a: Phase, phase_b: Phase) {
// no need to wait if the dependency is already done!
if let Some(Status::Done) = self.status.get(&(b, phase_b)) {
return;
}
let key = (a, phase_a);
let value = (b, phase_b);
match self.waiting_for.get_mut(&key) {
@ -275,7 +287,7 @@ impl Dependencies {
/// Struct storing various intermediate stages by their ModuleId
#[derive(Debug, Default)]
struct ModuleCache<'a> {
module_names: MutMap<ModuleId, ModuleName>,
module_names: MutMap<ModuleId, PQModuleName<'a>>,
/// Phases
headers: MutMap<ModuleId, ModuleHeader<'a>>,
@ -338,14 +350,16 @@ fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) ->
let dep_name = state
.module_cache
.module_names
.remove(&module_id)
.expect("module id is present");
.get(&module_id)
.expect("module id is present")
.clone();
BuildTask::LoadModule {
module_name: dep_name,
// Provide mutexes of ModuleIds and IdentIds by module,
// so other modules can populate them as they load.
module_ids: Arc::clone(&state.arc_modules),
shorthands: Arc::clone(&state.arc_shorthands),
ident_ids_by_module: Arc::clone(&state.ident_ids_by_module),
mode: state.stdlib.mode,
}
@ -394,8 +408,10 @@ fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) ->
// This should be small, and cloning it should be quick.
// We release the lock as soon as we're done cloning, so we don't have
// to lock the global module_ids while canonicalizing any given module.
let module_ids = Arc::clone(&state.arc_modules);
let module_ids = { (*module_ids).lock().clone() };
let qualified_module_ids = Arc::clone(&state.arc_modules);
let qualified_module_ids = { (*qualified_module_ids).lock().clone() };
let module_ids = qualified_module_ids.into_module_ids();
let exposed_symbols = state
.exposed_symbols_by_module
@ -407,9 +423,8 @@ fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) ->
for imported in parsed.imported_modules.iter() {
match state.module_cache.aliases.get(imported) {
None => unreachable!(
"imported module {:?} did not register its aliases, so {:?} cannot use them",
imported,
parsed.module_id,
r"imported module {:?} did not register its aliases, so {:?} cannot use them",
imported, parsed.module_id,
),
Some(new) => {
// TODO filter to only add imported aliases
@ -539,10 +554,12 @@ struct ModuleHeader<'a> {
module_name: AppOrInterfaceName<'a>,
module_path: PathBuf,
exposed_ident_ids: IdentIds,
deps_by_name: MutMap<ModuleName, ModuleId>,
deps_by_name: MutMap<PQModuleName<'a>, ModuleId>,
packages: MutMap<&'a str, PackageOrPath<'a>>,
imported_modules: MutSet<ModuleId>,
exposes: Vec<Symbol>,
exposed_imports: MutMap<Ident, (Symbol, Region)>,
to_platform: Option<To<'a>>,
src: &'a [u8],
module_timing: ModuleTiming,
}
@ -584,6 +601,7 @@ pub struct MonomorphizedModule<'a> {
pub interns: Interns,
pub subs: Subs,
pub output_path: Box<str>,
pub platform_path: Box<str>,
pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>,
pub mono_problems: MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>,
@ -606,7 +624,7 @@ struct ParsedModule<'a> {
module_path: PathBuf,
src: &'a str,
module_timing: ModuleTiming,
deps_by_name: MutMap<ModuleName, ModuleId>,
deps_by_name: MutMap<PQModuleName<'a>, ModuleId>,
imported_modules: MutSet<ModuleId>,
exposed_ident_ids: IdentIds,
exposed_imports: MutMap<Ident, (Symbol, Region)>,
@ -676,6 +694,8 @@ struct State<'a> {
pub stdlib: StdLib,
pub exposed_types: SubsByModule,
pub output_path: Option<&'a str>,
pub platform_path: Option<To<'a>>,
pub opt_effect_module: Option<ModuleId>,
pub headers_parsed: MutSet<ModuleId>,
@ -689,7 +709,8 @@ struct State<'a> {
pub constrained_ident_ids: MutMap<ModuleId, IdentIds>,
/// From now on, these will be used by multiple threads; time to make an Arc<Mutex<_>>!
pub arc_modules: Arc<Mutex<ModuleIds>>,
pub arc_modules: Arc<Mutex<PackageModuleIds<'a>>>,
pub arc_shorthands: Arc<Mutex<MutMap<&'a str, PackageOrPath<'a>>>>,
pub ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
@ -817,13 +838,15 @@ impl ModuleTiming {
#[allow(dead_code)]
enum BuildTask<'a> {
LoadModule {
module_name: ModuleName,
module_ids: Arc<Mutex<ModuleIds>>,
module_name: PQModuleName<'a>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
shorthands: Arc<Mutex<MutMap<&'a str, PackageOrPath<'a>>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
mode: Mode,
},
LoadPkgConfig {
module_ids: Arc<Mutex<ModuleIds>>,
shorthand: &'a str,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
mode: Mode,
},
@ -877,6 +900,7 @@ pub enum LoadingProblem {
FileProblem {
filename: PathBuf,
error: io::ErrorKind,
msg: &'static str,
},
ParsingFailed {
filename: PathBuf,
@ -988,7 +1012,7 @@ pub fn load_and_monomorphize_from_str<'a>(
}
struct LoadStart<'a> {
pub arc_modules: Arc<Mutex<ModuleIds>>,
pub arc_modules: Arc<Mutex<PackageModuleIds<'a>>>,
pub ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
pub root_id: ModuleId,
pub root_msg: Msg<'a>,
@ -1000,7 +1024,7 @@ impl<'a> LoadStart<'a> {
filename: PathBuf,
mode: Mode,
) -> Result<Self, LoadingProblem> {
let arc_modules = Arc::new(Mutex::new(ModuleIds::default()));
let arc_modules = Arc::new(Mutex::new(PackageModuleIds::default()));
let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids));
@ -1011,6 +1035,7 @@ impl<'a> LoadStart<'a> {
load_filename(
arena,
filename,
None,
Arc::clone(&arc_modules),
Arc::clone(&ident_ids_by_module),
root_start_time,
@ -1032,7 +1057,7 @@ impl<'a> LoadStart<'a> {
src: &'a str,
mode: Mode,
) -> Result<Self, LoadingProblem> {
let arc_modules = Arc::new(Mutex::new(ModuleIds::default()));
let arc_modules = Arc::new(Mutex::new(PackageModuleIds::default()));
let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids));
@ -1126,6 +1151,8 @@ where
root_msg,
} = load_start;
let arc_shorthands = Arc::new(Mutex::new(MutMap::default()));
let (msg_tx, msg_rx) = bounded(1024);
msg_tx
.send(root_msg)
@ -1248,6 +1275,8 @@ where
goal_phase,
stdlib,
output_path: None,
platform_path: None,
opt_effect_module: None,
module_cache: ModuleCache::default(),
dependencies: Dependencies::default(),
procedures: MutMap::default(),
@ -1256,6 +1285,7 @@ where
headers_parsed,
loading_started,
arc_modules,
arc_shorthands,
constrained_ident_ids: IdentIds::exposed_builtins(0),
ident_ids_by_module,
declarations_by_id: MutMap::default(),
@ -1388,6 +1418,16 @@ fn update<'a>(
log!("loaded header for {:?}", header.module_id);
let home = header.module_id;
{
let mut shorthands = (*state.arc_shorthands).lock();
for (shorthand, package_or_path) in header.packages.iter() {
shorthands.insert(shorthand, package_or_path.clone());
}
}
state.platform_path = state.platform_path.or_else(|| header.to_platform.clone());
// store an ID to name mapping, so we know the file to read when fetching dependencies' headers
for (name, id) in header.deps_by_name.iter() {
state.module_cache.module_names.insert(*id, name.clone());
@ -1412,6 +1452,7 @@ fn update<'a>(
let work = state.dependencies.add_module(
header.module_id,
state.opt_effect_module,
&header.imported_modules,
state.goal_phase,
);
@ -1499,6 +1540,9 @@ fn update<'a>(
module_docs,
} => {
let module_id = constrained_module.module.module_id;
state.opt_effect_module = Some(module_id);
log!("made effect module for {:?}", module_id);
state
.module_cache
@ -1761,7 +1805,8 @@ fn finish_specialization<'a>(
) -> MonomorphizedModule<'a> {
let module_ids = Arc::try_unwrap(state.arc_modules)
.unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids"))
.into_inner();
.into_inner()
.into_module_ids();
let interns = Interns {
module_ids,
@ -1772,6 +1817,7 @@ fn finish_specialization<'a>(
procedures,
module_cache,
output_path,
platform_path,
..
} = state;
@ -1788,11 +1834,33 @@ fn finish_specialization<'a>(
.map(|(id, (path, src))| (id, (path, src.into())))
.collect();
let path_to_platform = {
let package_or_path = match platform_path {
Some(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!"),
};
match package_or_path {
PackageOrPath::Path(StrLiteral::PlainLine(path)) => path,
PackageOrPath::Path(_) => unreachable!("invalid"),
_ => todo!("packages"),
}
};
let platform_path = path_to_platform.into();
MonomorphizedModule {
can_problems,
mono_problems,
type_problems,
output_path: output_path.unwrap_or(DEFAULT_APP_OUTPUT_PATH).into(),
platform_path,
exposed_to_host,
module_id: state.root_id,
subs,
@ -1811,7 +1879,8 @@ fn finish<'a>(
) -> LoadedModule {
let module_ids = Arc::try_unwrap(state.arc_modules)
.unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids"))
.into_inner();
.into_inner()
.into_module_ids();
let interns = Interns {
module_ids,
@ -1843,19 +1912,14 @@ fn finish<'a>(
fn load_pkg_config<'a>(
arena: &'a Bump,
src_dir: &Path,
module_ids: Arc<Mutex<ModuleIds>>,
shorthand: &'a str,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
mode: Mode,
) -> Result<Msg<'a>, LoadingProblem> {
let module_start_time = SystemTime::now();
let mut filename = PathBuf::from(src_dir);
filename.push("platform");
filename.push(PKG_CONFIG_FILE_NAME);
// End with .roc
filename.set_extension(ROC_FILE_EXTENSION);
let filename = PathBuf::from(src_dir);
let file_io_start = SystemTime::now();
let file = fs::read(&filename);
@ -1889,6 +1953,7 @@ fn load_pkg_config<'a>(
}
Ok((ast::Module::Platform { header }, _parse_state)) => fabricate_effects_module(
arena,
shorthand,
module_ids,
ident_ids_by_module,
mode,
@ -1903,6 +1968,7 @@ fn load_pkg_config<'a>(
Err(err) => Err(LoadingProblem::FileProblem {
filename,
error: err.kind(),
msg: "while reading a Pkg-Config.roc file",
}),
}
}
@ -1911,8 +1977,9 @@ fn load_pkg_config<'a>(
fn load_module<'a>(
arena: &'a Bump,
src_dir: &Path,
module_name: ModuleName,
module_ids: Arc<Mutex<ModuleIds>>,
module_name: PQModuleName<'a>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
arc_shorthands: Arc<Mutex<MutMap<&'a str, PackageOrPath<'a>>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
mode: Mode,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> {
@ -1921,10 +1988,36 @@ fn load_module<'a>(
filename.push(src_dir);
let opt_shorthand;
match module_name {
PQModuleName::Unqualified(name) => {
opt_shorthand = None;
// Convert dots in module name to directories
for part in module_name.as_str().split(MODULE_SEPARATOR) {
for part in name.split(MODULE_SEPARATOR) {
filename.push(part);
}
}
PQModuleName::Qualified(shorthand, name) => {
opt_shorthand = Some(shorthand);
let shorthands = arc_shorthands.lock();
match shorthands.get(shorthand) {
Some(PackageOrPath::Path(StrLiteral::PlainLine(path))) => {
filename.push(path);
}
Some(PackageOrPath::Path(_str_liteal)) => {
unreachable!("invalid structure for path")
}
Some(PackageOrPath::Package(_name, _version)) => todo!("packages"),
None => unreachable!("there is no shorthand named {:?}", shorthand),
}
// Convert dots in module name to directories
for part in name.split(MODULE_SEPARATOR) {
filename.push(part);
}
}
}
// End with .roc
filename.set_extension(ROC_FILE_EXTENSION);
@ -1932,6 +2025,7 @@ fn load_module<'a>(
load_filename(
arena,
filename,
opt_shorthand,
module_ids,
ident_ids_by_module,
module_start_time,
@ -1970,7 +2064,8 @@ fn parse_header<'a>(
arena: &'a Bump,
read_file_duration: Duration,
filename: PathBuf,
module_ids: Arc<Mutex<ModuleIds>>,
opt_shorthand: Option<&'a str>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
mode: Mode,
src_bytes: &'a [u8],
@ -1994,8 +2089,11 @@ fn parse_header<'a>(
value: AppOrInterfaceName::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,
@ -2005,30 +2103,63 @@ fn parse_header<'a>(
let mut pkg_config_dir = filename.clone();
pkg_config_dir.pop();
let packages = header.packages.into_bump_slice();
let (module_id, app_module_header_msg) = send_header(
Located {
region: header.name.region,
value: AppOrInterfaceName::App(header.name.value),
},
filename,
opt_shorthand,
packages,
header.provides.into_bump_slice(),
header.imports.into_bump_slice(),
Some(header.to.value.clone()),
parse_state,
module_ids.clone(),
ident_ids_by_module.clone(),
module_timing,
);
match header.to.value {
To::ExistingPackage(existing_package) => {
let opt_base_package = packages.iter().find(|loc_package_entry| {
let Located { value, .. } = loc_package_entry;
match value {
PackageEntry::Entry { shorthand, .. } => shorthand == &existing_package,
_ => false,
}
});
match opt_base_package {
Some(Located {
value:
PackageEntry::Entry {
shorthand,
package_or_path:
Located {
value: package_or_path,
..
},
..
},
..
}) => {
match package_or_path {
PackageOrPath::Path(StrLiteral::PlainLine(package)) => {
// check whether we can find a Pkg-Config.roc file
let mut pkg_config_roc = pkg_config_dir.clone();
pkg_config_roc.push("platform");
let mut pkg_config_roc = pkg_config_dir;
pkg_config_roc.push(package);
pkg_config_roc.push(PKG_CONFIG_FILE_NAME);
pkg_config_roc.set_extension(ROC_FILE_EXTENSION);
if pkg_config_roc.as_path().exists() {
let load_pkg_config_msg = load_pkg_config(
arena,
&pkg_config_dir,
&pkg_config_roc,
shorthand,
module_ids,
ident_ids_by_module,
mode,
@ -2036,14 +2167,38 @@ fn parse_header<'a>(
Ok((
module_id,
Msg::Many(vec![app_module_header_msg, load_pkg_config_msg]),
Msg::Many(vec![
app_module_header_msg,
load_pkg_config_msg,
]),
))
} else {
Ok((module_id, app_module_header_msg))
}
}
_ => unreachable!(),
}
}
_ => panic!("could not find base"),
}
}
To::NewPackage(package_or_path) => match package_or_path {
PackageOrPath::Package(_, _) => panic!("TODO implement packages"),
PackageOrPath::Path(StrLiteral::PlainLine(_package)) => {
Ok((module_id, app_module_header_msg))
}
PackageOrPath::Path(StrLiteral::Block(_)) => {
panic!("TODO implement block package path")
}
PackageOrPath::Path(StrLiteral::Line(_)) => {
panic!("TODO implement line package path")
}
},
}
}
Ok((ast::Module::Platform { header }, _parse_state)) => fabricate_effects_module(
arena,
&"",
module_ids,
ident_ids_by_module,
mode,
@ -2058,7 +2213,8 @@ fn parse_header<'a>(
fn load_filename<'a>(
arena: &'a Bump,
filename: PathBuf,
module_ids: Arc<Mutex<ModuleIds>>,
opt_shorthand: Option<&'a str>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
module_start_time: SystemTime,
mode: Mode,
@ -2072,6 +2228,7 @@ fn load_filename<'a>(
arena,
file_io_duration,
filename,
opt_shorthand,
module_ids,
ident_ids_by_module,
mode,
@ -2081,17 +2238,19 @@ fn load_filename<'a>(
Err(err) => Err(LoadingProblem::FileProblem {
filename,
error: err.kind(),
msg: "in `load_filename`",
}),
}
}
/// Load a module from a str
/// the `filename` is never read, but used for the module name
#[allow(clippy::too_many_arguments)]
fn load_from_str<'a>(
arena: &'a Bump,
filename: PathBuf,
src: &'a str,
module_ids: Arc<Mutex<ModuleIds>>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
module_start_time: SystemTime,
mode: Mode,
@ -2103,6 +2262,7 @@ fn load_from_str<'a>(
arena,
file_io_duration,
filename,
None,
module_ids,
ident_ids_by_module,
mode,
@ -2122,10 +2282,13 @@ enum AppOrInterfaceName<'a> {
fn send_header<'a>(
loc_name: Located<AppOrInterfaceName<'a>>,
filename: PathBuf,
opt_shorthand: Option<&'a str>,
packages: &'a [Located<PackageEntry<'a>>],
exposes: &'a [Located<ExposesEntry<'a, &'a str>>],
imports: &'a [Located<ImportsEntry<'a>>],
to_platform: Option<To<'a>>,
parse_state: parser::State<'a>,
module_ids: Arc<Mutex<ModuleIds>>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
module_timing: ModuleTiming,
) -> (ModuleId, Msg<'a>) {
@ -2141,20 +2304,21 @@ fn send_header<'a>(
}
};
let mut imported: Vec<(ModuleName, Vec<Ident>, Region)> = Vec::with_capacity(imports.len());
let mut imported: Vec<(QualifiedModuleName, Vec<Ident>, Region)> =
Vec::with_capacity(imports.len());
let mut imported_modules: MutSet<ModuleId> = MutSet::default();
let mut scope_size = 0;
for loc_entry in imports {
let (module_name, exposed) = exposed_from_import(&loc_entry.value);
let (qualified_module_name, exposed) = exposed_from_import(&loc_entry.value);
scope_size += exposed.len();
imported.push((module_name, exposed, loc_entry.region));
imported.push((qualified_module_name, exposed, loc_entry.region));
}
let num_exposes = exposes.len();
let mut deps_by_name: MutMap<ModuleName, ModuleId> =
let mut deps_by_name: MutMap<PQModuleName, ModuleId> =
HashMap::with_capacity_and_hasher(num_exposes, default_hasher());
let mut exposed: Vec<Symbol> = Vec::with_capacity(num_exposes);
@ -2169,7 +2333,13 @@ fn send_header<'a>(
let mut module_ids = (*module_ids).lock();
let mut ident_ids_by_module = (*ident_ids_by_module).lock();
home = module_ids.get_or_insert(&declared_name.as_inline_str());
let name = match opt_shorthand {
Some(shorthand) => {
PQModuleName::Qualified(&shorthand, declared_name.as_inline_str().clone())
}
None => PQModuleName::Unqualified(declared_name.as_inline_str().clone()),
};
home = module_ids.get_or_insert(&name);
// Ensure this module has an entry in the exposed_ident_ids map.
ident_ids_by_module
@ -2178,17 +2348,28 @@ fn send_header<'a>(
// For each of our imports, add an entry to deps_by_name
//
// e.g. for `imports [ Foo.{ bar } ]`, add `Foo` to deps_by_name
// e.g. for `imports [ base.Foo.{ bar } ]`, add `Foo` to deps_by_name
//
// Also build a list of imported_values_to_expose (like `bar` above.)
for (module_name, exposed_idents, region) in imported.into_iter() {
let cloned_module_name = module_name.clone();
let module_id = module_ids.get_or_insert(&module_name.into());
deps_by_name.insert(cloned_module_name, module_id);
for (qualified_module_name, exposed_idents, region) in imported.into_iter() {
let cloned_module_name = qualified_module_name.module.clone();
let pq_module_name = match qualified_module_name.opt_package {
None => match opt_shorthand {
Some(shorthand) => {
PQModuleName::Qualified(shorthand, qualified_module_name.module.into())
}
None => PQModuleName::Unqualified(qualified_module_name.module.into()),
},
Some(package) => {
PQModuleName::Qualified(package, cloned_module_name.clone().into())
}
};
let module_id = module_ids.get_or_insert(&pq_module_name);
imported_modules.insert(module_id);
deps_by_name.insert(pq_module_name, module_id);
// Add the new exposed idents to the dep module's IdentIds, so
// once that module later gets loaded, its lookups will resolve
// to the same symbols as the ones we're using here.
@ -2236,6 +2417,25 @@ fn send_header<'a>(
ident_ids.clone()
};
let mut parse_entries: Vec<_> = (&packages).iter().map(|x| &x.value).collect();
let mut package_entries = MutMap::default();
while let Some(parse_entry) = parse_entries.pop() {
use PackageEntry::*;
match parse_entry {
Entry {
shorthand,
package_or_path,
..
} => {
package_entries.insert(*shorthand, package_or_path.value.clone());
}
SpaceBefore(inner, _) | SpaceAfter(inner, _) => {
parse_entries.push(inner);
}
}
}
// Send the deps to the coordinator thread for processing,
// then continue on to parsing and canonicalizing defs.
//
@ -2249,9 +2449,11 @@ fn send_header<'a>(
module_path: filename,
exposed_ident_ids: ident_ids,
module_name: loc_name.value,
packages: package_entries,
imported_modules,
deps_by_name,
exposes: exposed,
to_platform,
src: parse_state.bytes,
exposed_imports: scope,
module_timing,
@ -2370,9 +2572,11 @@ fn run_solve<'a>(
}
}
#[allow(clippy::too_many_arguments)]
fn fabricate_effects_module<'a>(
arena: &'a Bump,
module_ids: Arc<Mutex<ModuleIds>>,
shorthand: &'a str,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
mode: Mode,
header: PlatformHeader<'a>,
@ -2398,12 +2602,23 @@ fn fabricate_effects_module<'a>(
functions
};
{
let mut module_ids = (*module_ids).lock();
for exposed in header.exposes {
if let ExposesEntry::Exposed(module_name) = exposed.value {
module_ids.get_or_insert(&PQModuleName::Qualified(shorthand, module_name.into()));
}
}
}
let exposed_ident_ids = {
// Lock just long enough to perform the minimal operations necessary.
let mut module_ids = (*module_ids).lock();
let mut ident_ids_by_module = (*ident_ids_by_module).lock();
module_id = module_ids.get_or_insert(&declared_name.as_inline_str());
let name = PQModuleName::Qualified(shorthand, declared_name.as_inline_str().clone());
module_id = module_ids.get_or_insert(&name);
// Ensure this module has an entry in the exposed_ident_ids map.
ident_ids_by_module
@ -2458,7 +2673,7 @@ fn fabricate_effects_module<'a>(
let mut var_store = VarStore::default();
let module_ids = { (*module_ids).lock().clone() };
let module_ids = { (*module_ids).lock().clone() }.into_module_ids();
let mut scope = roc_can::scope::Scope::new(module_id, &mut var_store);
let mut can_env = roc_can::env::Env::new(module_id, dep_idents, &module_ids, exposed_ident_ids);
@ -2782,7 +2997,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, Loadi
Ok(Msg::Parsed(parsed))
}
fn exposed_from_import(entry: &ImportsEntry<'_>) -> (ModuleName, Vec<Ident>) {
fn exposed_from_import<'a>(entry: &ImportsEntry<'a>) -> (QualifiedModuleName<'a>, Vec<Ident>) {
use roc_parse::header::ImportsEntry::*;
match entry {
@ -2793,11 +3008,27 @@ fn exposed_from_import(entry: &ImportsEntry<'_>) -> (ModuleName, Vec<Ident>) {
exposed.push(ident_from_exposed(&loc_entry.value));
}
(module_name.as_str().into(), exposed)
let qualified_module_name = QualifiedModuleName {
opt_package: None,
module: module_name.as_str().into(),
};
(qualified_module_name, exposed)
}
Package(_package_name, _module_name, _exposes) => {
todo!("TODO support exposing package-qualified module names.");
Package(package_name, module_name, exposes) => {
let mut exposed = Vec::with_capacity(exposes.len());
for loc_entry in exposes {
exposed.push(ident_from_exposed(&loc_entry.value));
}
let qualified_module_name = QualifiedModuleName {
opt_package: Some(package_name),
module: module_name.as_str().into(),
};
(qualified_module_name, exposed)
}
SpaceBefore(sub_entry, _) | SpaceAfter(sub_entry, _) => {
@ -3074,6 +3305,7 @@ fn run_task<'a>(
LoadModule {
module_name,
module_ids,
shorthands,
ident_ids_by_module,
mode,
} => load_module(
@ -3081,15 +3313,24 @@ fn run_task<'a>(
src_dir,
module_name,
module_ids,
shorthands,
ident_ids_by_module,
mode,
)
.map(|(_, msg)| msg),
LoadPkgConfig {
shorthand,
module_ids,
ident_ids_by_module,
mode,
} => load_pkg_config(arena, src_dir, module_ids, ident_ids_by_module, mode),
} => load_pkg_config(
arena,
src_dir,
shorthand,
module_ids,
ident_ids_by_module,
mode,
),
Parse { header } => parse(arena, header),
CanonicalizeAndConstrain {
parsed,

View file

@ -1,6 +1,6 @@
app "quicksort" provides [ swap, partition, partitionHelp, quicksort ] to "./platform"
quicksort : List (Num a), Int, Int -> List (Num a)
quicksort : List (Num a), I64, I64 -> List (Num a)
quicksort = \list, low, high ->
when partition low high list is
Pair partitionIndex partitioned ->
@ -9,7 +9,7 @@ quicksort = \list, low, high ->
|> quicksort (partitionIndex + 1) high
swap : Int, Int, List a -> List a
swap : I64, I64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -21,7 +21,7 @@ swap = \i, j, list ->
[]
partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -33,7 +33,7 @@ partition = \low, high, initialList ->
Pair (low - 1) initialList
partitionHelp : Int, Int, List (Num a), Int, (Num a) -> [ Pair Int (List (Num a)) ]
partitionHelp : I64, I64, List (Num a), I64, (Num a) -> [ Pair I64 (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is

View file

@ -1,7 +1,7 @@
app "quicksort" provides [ quicksort ] to "./platform"
quicksort = \originalList ->
quicksortHelp : List (Num a), Int, Int -> List (Num a)
quicksortHelp : List (Num a), I64, I64 -> List (Num a)
quicksortHelp = \list, low, high ->
if low < high then
when partition low high list is
@ -13,7 +13,7 @@ quicksort = \originalList ->
list
swap : Int, Int, List a -> List a
swap : I64, I64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -24,7 +24,7 @@ quicksort = \originalList ->
_ ->
[]
partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -36,7 +36,7 @@ quicksort = \originalList ->
Pair (low - 1) initialList
partitionHelp : Int, Int, List (Num a), Int, (Num a) -> [ Pair Int (List (Num a)) ]
partitionHelp : I64, I64, List (Num a), I64, (Num a) -> [ Pair I64 (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is

View file

@ -2,7 +2,7 @@ interface Quicksort
exposes [ swap, partition, quicksort ]
imports []
quicksort : List (Num a), Int, Int -> List (Num a)
quicksort : List (Num a), I64, I64 -> List (Num a)
quicksort = \list, low, high ->
when partition low high list is
Pair partitionIndex partitioned ->
@ -11,7 +11,7 @@ quicksort = \list, low, high ->
|> quicksort (partitionIndex + 1) high
swap : Int, Int, List a -> List a
swap : I64, I64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -23,7 +23,7 @@ swap = \i, j, list ->
[]
partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -35,7 +35,7 @@ partition = \low, high, initialList ->
Pair (low - 1) initialList
partitionHelp : Int, Int, List (Num a), Int, (Num a) -> [ Pair Int (List (Num a)) ]
partitionHelp : I64, I64, List (Num a), I64, (Num a) -> [ Pair I64 (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is

View file

@ -265,7 +265,7 @@ mod test_load {
imports [ RBTree ]
provides [ main ] to blah
empty : RBTree.Dict Int Int
empty : RBTree.Dict I64 I64
empty = RBTree.empty
main = empty
@ -360,7 +360,7 @@ mod test_load {
"floatTest" => "F64",
"divisionFn" => "F64, F64 -> Result F64 [ DivByZero ]*",
"divisionTest" => "Result F64 [ DivByZero ]*",
"intTest" => "Int",
"intTest" => "I64",
"x" => "F64",
"constantNum" => "Num *",
"divDep1ByDep2" => "Result F64 [ DivByZero ]*",
@ -377,10 +377,10 @@ mod test_load {
expect_types(
loaded_module,
hashmap! {
"swap" => "Int, Int, List a -> List a",
"partition" => "Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]",
"partitionHelp" => "Int, Int, List (Num a), Int, Num a -> [ Pair Int (List (Num a)) ]",
"quicksort" => "List (Num a), Int, Int -> List (Num a)",
"swap" => "I64, I64, List a -> List a",
"partition" => "I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ]",
"partitionHelp" => "I64, I64, List (Num a), I64, Num a -> [ Pair I64 (List (Num a)) ]",
"quicksort" => "List (Num a), I64, I64 -> List (Num a)",
},
);
}
@ -406,10 +406,10 @@ mod test_load {
expect_types(
loaded_module,
hashmap! {
"swap" => "Int, Int, List a -> List a",
"partition" => "Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]",
"partitionHelp" => "Int, Int, List (Num a), Int, Num a -> [ Pair Int (List (Num a)) ]",
"quicksort" => "List (Num a), Int, Int -> List (Num a)",
"swap" => "I64, I64, List a -> List a",
"partition" => "I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ]",
"partitionHelp" => "I64, I64, List (Num a), I64, Num a -> [ Pair I64 (List (Num a)) ]",
"quicksort" => "List (Num a), I64, I64 -> List (Num a)",
},
);
}

View file

@ -235,7 +235,7 @@ mod test_uniq_load {
"floatTest" => "Attr Shared F64",
"divisionFn" => "Attr Shared (Attr * F64, Attr * F64 -> Attr * (Result (Attr * F64) (Attr * [ DivByZero ]*)))",
"divisionTest" => "Attr * (Result (Attr * F64) (Attr * [ DivByZero ]*))",
"intTest" => "Attr * Int",
"intTest" => "Attr * I64",
"x" => "Attr * F64",
"constantNum" => "Attr * (Num (Attr * *))",
"divDep1ByDep2" => "Attr * (Result (Attr * F64) (Attr * [ DivByZero ]*))",
@ -271,11 +271,11 @@ mod test_uniq_load {
expect_types(
loaded_module,
hashmap! {
"swap" => "Attr * (Attr * Int, Attr * Int, Attr * (List (Attr Shared a)) -> Attr * (List (Attr Shared a)))",
"partition" => "Attr * (Attr Shared Int, Attr Shared Int, Attr b (List (Attr Shared (Num (Attr Shared a)))) -> Attr * [ Pair (Attr * Int) (Attr b (List (Attr Shared (Num (Attr Shared a))))) ])",
"swap" => "Attr * (Attr * I64, Attr * I64, Attr * (List (Attr Shared a)) -> Attr * (List (Attr Shared a)))",
"partition" => "Attr * (Attr Shared I64, Attr Shared I64, Attr b (List (Attr Shared (Num (Attr Shared a)))) -> Attr * [ Pair (Attr * I64) (Attr b (List (Attr Shared (Num (Attr Shared a))))) ])",
"partitionHelp" => "Attr Shared (Attr b Int, Attr Shared Int, Attr c (List (Attr Shared (Num (Attr Shared a)))), Attr Shared Int, Attr Shared (Num (Attr Shared a)) -> Attr * [ Pair (Attr b Int) (Attr c (List (Attr Shared (Num (Attr Shared a))))) ])",
"quicksort" => "Attr Shared (Attr b (List (Attr Shared (Num (Attr Shared a)))), Attr Shared Int, Attr Shared Int -> Attr b (List (Attr Shared (Num (Attr Shared a)))))",
"partitionHelp" => "Attr Shared (Attr b I64, Attr Shared I64, Attr c (List (Attr Shared (Num (Attr Shared a)))), Attr Shared I64, Attr Shared (Num (Attr Shared a)) -> Attr * [ Pair (Attr b I64) (Attr c (List (Attr Shared (Num (Attr Shared a))))) ])",
"quicksort" => "Attr Shared (Attr b (List (Attr Shared (Num (Attr Shared a)))), Attr Shared I64, Attr Shared I64 -> Attr b (List (Attr Shared (Num (Attr Shared a)))))",
},
);
}

View file

@ -12,6 +12,11 @@ impl Ident {
}
}
pub struct QualifiedModuleName<'a> {
pub opt_package: Option<&'a str>,
pub module: ModuleName,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ModuleName(InlinableString);

View file

@ -310,6 +310,107 @@ impl fmt::Debug for ModuleId {
}
}
/// base.Task
/// 1. build mapping from short name to package
/// 2. when adding new modules from package we need to register them in some other map (this module id goes with short name) (shortname, module-name) -> moduleId
/// 3. pass this around to other modules getting headers parsed. when parsing interfaces we need to use this map to reference shortnames
/// 4. throw away short names. stash the module id in the can env under the resolved module name
/// 5. test:
/// Package-qualified module name
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum PQModuleName<'a> {
Unqualified(InlinableString),
Qualified(&'a str, InlinableString),
}
impl<'a> PQModuleName<'a> {
pub fn to_module_name(&self) -> &InlinableString {
match self {
PQModuleName::Unqualified(name) => name,
PQModuleName::Qualified(_, name) => name,
}
}
}
#[derive(Debug, Clone)]
pub struct PackageModuleIds<'a> {
by_name: MutMap<PQModuleName<'a>, ModuleId>,
by_id: Vec<PQModuleName<'a>>,
}
impl<'a> PackageModuleIds<'a> {
pub fn get_or_insert(&mut self, module_name: &PQModuleName<'a>) -> ModuleId {
match self.by_name.get(module_name) {
Some(id) => *id,
None => {
let by_id = &mut self.by_id;
let module_id = ModuleId(by_id.len() as u32);
by_id.push(module_name.clone());
self.by_name.insert(module_name.clone(), module_id);
if cfg!(debug_assertions) {
Self::insert_debug_name(module_id, &module_name);
}
module_id
}
}
}
pub fn into_module_ids(self) -> ModuleIds {
let by_name: MutMap<InlinableString, ModuleId> = self
.by_name
.into_iter()
.map(|(pqname, module_id)| (pqname.to_module_name().clone(), module_id))
.collect();
let by_id: Vec<InlinableString> = self
.by_id
.into_iter()
.map(|pqname| pqname.to_module_name().clone())
.collect();
ModuleIds { by_name, by_id }
}
#[cfg(debug_assertions)]
fn insert_debug_name(module_id: ModuleId, module_name: &PQModuleName) {
let mut names = DEBUG_MODULE_ID_NAMES.lock().expect("Failed to acquire lock for Debug interning into DEBUG_MODULE_ID_NAMES, presumably because a thread panicked.");
if !names.contains_key(&module_id.0) {
match module_name {
PQModuleName::Unqualified(module) => {
names.insert(module_id.0, module.to_string().into());
}
PQModuleName::Qualified(package, module) => {
let name = format!("{}.{}", package, module).into();
names.insert(module_id.0, name);
}
}
}
}
#[cfg(not(debug_assertions))]
fn insert_debug_name(_module_id: ModuleId, _module_name: &PQModuleName) {
// By design, this is a no-op in release builds!
}
pub fn get_id(&self, module_name: &PQModuleName<'a>) -> Option<&ModuleId> {
self.by_name.get(module_name)
}
pub fn get_name(&self, id: ModuleId) -> Option<&PQModuleName> {
self.by_id.get(id.0 as usize)
}
pub fn available_modules(&self) -> impl Iterator<Item = &PQModuleName> {
self.by_id.iter()
}
}
/// Stores a mapping between ModuleId and InlinableString.
///
/// Each module name is stored twice, for faster lookups.
@ -346,8 +447,11 @@ impl ModuleIds {
fn insert_debug_name(module_id: ModuleId, module_name: &InlinableString) {
let mut names = DEBUG_MODULE_ID_NAMES.lock().expect("Failed to acquire lock for Debug interning into DEBUG_MODULE_ID_NAMES, presumably because a thread panicked.");
// TODO make sure modules are never added more than once!
if !names.contains_key(&module_id.0) {
names.insert(module_id.0, module_name.to_string().into());
}
}
#[cfg(not(debug_assertions))]
fn insert_debug_name(_module_id: ModuleId, _module_name: &InlinableString) {
@ -495,7 +599,8 @@ macro_rules! define_builtins {
if cfg!(debug_assertions) {
let module_id = ModuleId($module_id);
ModuleIds::insert_debug_name(module_id, &$module_name.into());
let name = PQModuleName::Unqualified($module_name.into());
PackageModuleIds::insert_debug_name(module_id, &name);
module_id.register_debug_idents(&ident_ids);
}
@ -551,6 +656,34 @@ macro_rules! define_builtins {
}
}
impl<'a> Default for PackageModuleIds<'a> {
fn default() -> Self {
// +1 because the user will be compiling at least 1 non-builtin module!
let capacity = $total + 1;
let mut by_name = HashMap::with_capacity_and_hasher(capacity, default_hasher());
let mut by_id = Vec::with_capacity(capacity);
let mut insert_both = |id: ModuleId, name_str: &'static str| {
let raw_name: InlinableString = name_str.into();
let name = PQModuleName::Unqualified(raw_name);
if cfg!(debug_assertions) {
Self::insert_debug_name(id, &name);
}
by_name.insert(name.clone(), id);
by_id.push(name);
};
$(
insert_both(ModuleId($module_id), $module_name);
)+
PackageModuleIds { by_name, by_id }
}
}
impl Symbol {
$(
$(
@ -609,7 +742,7 @@ define_builtins! {
1 NUM: "Num" => {
0 NUM_NUM: "Num" imported // the Num.Num type alias
1 NUM_AT_NUM: "@Num" // the Num.@Num private tag
2 NUM_INT: "Int" imported // the Int.Int type alias
2 NUM_I64: "I64" imported // the Num.I64 type alias
3 NUM_INTEGER: "Integer" imported // Int : Num Integer
4 NUM_AT_INTEGER: "@Integer" // the Int.@Integer private tag
5 NUM_F64: "F64" imported // the Num.F64 type alias

View file

@ -323,7 +323,7 @@ impl<'a> Layout<'a> {
}
Structure(flat_type) => layout_from_flat_type(env, flat_type),
Alias(Symbol::NUM_INT, args, _) => {
Alias(Symbol::NUM_I64, args, _) => {
debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Int64))
}
@ -723,7 +723,7 @@ fn layout_from_flat_type<'a>(
match flat_type {
Apply(symbol, args) => {
match symbol {
Symbol::NUM_INT => {
Symbol::NUM_I64 => {
debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Int64))
}

View file

@ -375,7 +375,7 @@ mod test_mono {
fn ir_when_just() {
compiles_to_ir(
r#"
x : [ Nothing, Just Int ]
x : [ Nothing, Just I64 ]
x = Just 41
when x is
@ -412,7 +412,7 @@ mod test_mono {
fn one_element_tag() {
compiles_to_ir(
r#"
x : [ Pair Int ]
x : [ Pair I64 ]
x = Pair 2
x
@ -502,7 +502,7 @@ mod test_mono {
r#"
Maybe a : [ Nothing, Just a ]
x : Maybe (Maybe Int)
x : Maybe (Maybe I64)
x = Just (Just 41)
when x is
@ -795,7 +795,7 @@ mod test_mono {
compiles_to_ir(
r#"
wrapper = \{} ->
x : Result Int Int
x : Result I64 I64
x = Ok 2
y =
@ -1002,10 +1002,10 @@ mod test_mono {
compiles_to_ir(
indoc!(
r#"
x : List Int
x : List I64
x = [1,2,3]
id : List Int -> List Int
id : List I64 -> List I64
id = \y -> List.set y 0 0
id x
@ -1185,7 +1185,7 @@ mod test_mono {
compiles_to_ir(
indoc!(
r#"
quicksortHelp : List (Num a), Int, Int -> List (Num a)
quicksortHelp : List (Num a), I64, I64 -> List (Num a)
quicksortHelp = \list, low, high ->
if low < high then
(Pair partitionIndex partitioned) = Pair 0 []
@ -1376,7 +1376,7 @@ mod test_mono {
r#"
app "test" provides [ main ] to "./platform"
partitionHelp : Int, Int, List (Num a), Int, (Num a) -> [ Pair Int (List (Num a)) ]
partitionHelp : I64, I64, List (Num a), I64, (Num a) -> [ Pair I64 (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is
@ -1412,7 +1412,7 @@ mod test_mono {
r#"
app "test" provides [ main ] to "./platform"
quicksortHelp : List (Num a), Int, Int -> List (Num a)
quicksortHelp : List (Num a), I64, I64 -> List (Num a)
quicksortHelp = \list, low, high ->
if low < high then
(Pair partitionIndex partitioned) = partition low high list
@ -1424,7 +1424,7 @@ mod test_mono {
list
swap : Int, Int, List a -> List a
swap : I64, I64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -1435,7 +1435,7 @@ mod test_mono {
_ ->
[]
partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -1447,7 +1447,7 @@ mod test_mono {
Pair (low - 1) initialList
partitionHelp : Int, Int, List (Num a), Int, (Num a) -> [ Pair Int (List (Num a)) ]
partitionHelp : I64, I64, List (Num a), I64, (Num a) -> [ Pair I64 (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is
@ -1690,10 +1690,10 @@ mod test_mono {
r#"
app "test" provides [ main ] to "./platform"
x : List Int
x : List I64
x = [1,2,3]
add : List Int -> List Int
add : List I64 -> List I64
add = \y -> List.set y 0 0
main =
@ -1851,7 +1851,6 @@ mod test_mono {
let Test.2 = S Test.9 Test.8;
let Test.5 = 1i64;
let Test.6 = Index 0 Test.2;
dec Test.2;
let Test.7 = lowlevel Eq Test.5 Test.6;
if Test.7 then
let Test.3 = 0i64;
@ -1903,15 +1902,12 @@ mod test_mono {
let Test.10 = lowlevel Eq Test.8 Test.9;
if Test.10 then
let Test.4 = Index 1 Test.2;
dec Test.2;
let Test.3 = 1i64;
ret Test.3;
else
dec Test.2;
let Test.5 = 0i64;
ret Test.5;
else
dec Test.2;
let Test.6 = 0i64;
ret Test.6;
"#
@ -1996,7 +1992,7 @@ mod test_mono {
r#"
Maybe a : [ Nothing, Just a ]
x : Maybe (Maybe Int)
x : Maybe (Maybe I64)
x = Just (Just 41)
when x is
@ -2051,10 +2047,10 @@ mod test_mono {
r#"
LinkedList a : [ Nil, Cons a (LinkedList a) ]
nil : LinkedList Int
nil : LinkedList I64
nil = Nil
length : LinkedList a -> Int
length : LinkedList a -> I64
length = \list ->
when list is
Nil -> 0
@ -2106,7 +2102,7 @@ mod test_mono {
r#"
app "test" provides [ main ] to "./platform"
swap : Int, Int, List a -> List a
swap : I64, I64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->

View file

@ -1608,8 +1608,8 @@ fn to_diff<'b>(
let right = to_doc(alloc, Parens::Unnecessary, type2);
let is_int = |t: &ErrorType| match t {
ErrorType::Type(Symbol::NUM_INT, _) => true,
ErrorType::Alias(Symbol::NUM_INT, _, _) => true,
ErrorType::Type(Symbol::NUM_I64, _) => true,
ErrorType::Alias(Symbol::NUM_I64, _, _) => true,
ErrorType::Type(Symbol::NUM_NUM, args) => match &args.get(0) {
Some(ErrorType::Type(Symbol::NUM_INTEGER, _)) => true,

View file

@ -623,7 +623,7 @@ mod test_reporting {
Num
Set
Result
Int
F64
"#
),
);
@ -634,7 +634,7 @@ mod test_reporting {
// report_problem_as(
// indoc!(
// r#"
// foo : Int as Int
// foo : I64 as I64
// foo = 42
//
// foo
@ -657,7 +657,7 @@ mod test_reporting {
// report_problem_as(
// indoc!(
// r#"
// foo : Int as a
// foo : I64 as a
// foo = 42
//
// foo
@ -961,7 +961,7 @@ mod test_reporting {
r#"
bar = { bar : 0x3 }
f : { foo : Int } -> Bool
f : { foo : I64 } -> Bool
f = \_ -> True
f bar
@ -978,11 +978,11 @@ mod test_reporting {
This `bar` value is a:
{ bar : Int }
{ bar : I64 }
But `f` needs the 1st argument to be:
{ foo : Int }
{ foo : I64 }
Tip: Seems like a record field typo. Maybe `bar` should be `foo`?
@ -1037,7 +1037,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
f : [ Red Int, Green Bool ] -> Bool
f : [ Red I64, Green Bool ] -> Bool
f = \_ -> True
f (Blue 3.14)
@ -1058,7 +1058,7 @@ mod test_reporting {
But `f` needs the 1st argument to be:
[ Green Bool, Red Int ]
[ Green Bool, Red I64 ]
Tip: Seems like a tag typo. Maybe `Blue` should be `Red`?
@ -1075,7 +1075,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
x : Int
x : I64
x = if True then 3.14 else 4
x
@ -1096,7 +1096,7 @@ mod test_reporting {
But the type annotation on `x` says it should be:
Int
I64
Tip: You can convert between Int and Float using functions like
`Num.toFloat` and `Num.round`.
@ -1110,7 +1110,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
x : Int
x : I64
x =
when True is
_ -> 3.14
@ -1124,7 +1124,7 @@ mod test_reporting {
Something is off with the body of the `x` definition:
1 x : Int
1 x : I64
2 x =
3> when True is
4> _ -> 3.14
@ -1135,7 +1135,7 @@ mod test_reporting {
But the type annotation on `x` says it should be:
Int
I64
Tip: You can convert between Int and Float using functions like
`Num.toFloat` and `Num.round`.
@ -1149,7 +1149,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
x : Int -> Int
x : I64 -> I64
x = \_ -> 3.14
x
@ -1161,7 +1161,7 @@ mod test_reporting {
Something is off with the body of the `x` definition:
1 x : Int -> Int
1 x : I64 -> I64
2 x = \_ -> 3.14
^^^^
@ -1171,7 +1171,7 @@ mod test_reporting {
But the type annotation on `x` says it should be:
Int
I64
Tip: You can convert between Int and Float using functions like
`Num.toFloat` and `Num.round`.
@ -1185,7 +1185,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
x : Int
x : I64
x = 42
x 3
@ -1211,7 +1211,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
f : Int -> Int
f : I64 -> I64
f = \_ -> 42
f 1 2
@ -1237,7 +1237,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
f : Int, Int -> Int
f : I64, I64 -> I64
f = \_, _ -> 42
f 1
@ -1372,9 +1372,9 @@ mod test_reporting {
these names seem close though:
Bool
Int
F64
Num
Map
"#
),
)
@ -1483,7 +1483,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
{ x } : { x : Int }
{ x } : { x : I64 }
{ x } = { x: 4.0 }
x
@ -1495,7 +1495,7 @@ mod test_reporting {
Something is off with the body of this definition:
1 { x } : { x : Int }
1 { x } : { x : I64 }
2 { x } = { x: 4.0 }
^^^^^^^^^^
@ -1505,7 +1505,7 @@ mod test_reporting {
But the type annotation says it should be:
{ x : Int }
{ x : I64 }
Tip: You can convert between Int and Float using functions like
`Num.toFloat` and `Num.round`.
@ -1644,7 +1644,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
x : { a : Int, b : F64, c : Bool }
x : { a : I64, b : F64, c : Bool }
x = { b: 4.0 }
x
@ -1656,7 +1656,7 @@ mod test_reporting {
Something is off with the body of the `x` definition:
1 x : { a : Int, b : F64, c : Bool }
1 x : { a : I64, b : F64, c : Bool }
2 x = { b: 4.0 }
^^^^^^^^^^
@ -1666,7 +1666,7 @@ mod test_reporting {
But the type annotation on `x` says it should be:
{ a : Int, b : F64, c : Bool }
{ a : I64, b : F64, c : Bool }
Tip: Looks like the c and a fields are missing.
"#
@ -1788,7 +1788,7 @@ mod test_reporting {
The body is an integer of type:
Int
I64
But the type annotation on `f` says it should be:
@ -1796,7 +1796,7 @@ mod test_reporting {
Tip: The type annotation uses the type variable `msg` to say that this
definition can produce any type of value. But in the body I see that
it will only produce a `Int` value of a single specific type. Maybe
it will only produce a `I64` value of a single specific type. Maybe
change the type annotation to be more specific? Maybe change the code
to be more general?
"#
@ -1810,7 +1810,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
f : Bool -> [ Ok Int, InvalidFoo ]
f : Bool -> [ Ok I64, InvalidFoo ]
f = \_ -> ok 4
f
@ -1828,9 +1828,9 @@ mod test_reporting {
these names seem close though:
f
Int
F64
Num
Map
"#
),
)
@ -1842,7 +1842,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
f : Bool -> Int
f : Bool -> I64
f = \_ ->
ok = 3
@ -1867,7 +1867,7 @@ mod test_reporting {
Something is off with the body of the `f` definition:
1 f : Bool -> Int
1 f : Bool -> I64
2 f = \_ ->
3 ok = 3
4
@ -1880,7 +1880,7 @@ mod test_reporting {
But the type annotation on `f` says it should be:
Int
I64
"#
),
)
@ -2005,7 +2005,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
f : { fo: Int }ext -> Int
f : { fo: I64 }ext -> I64
f = \r ->
r2 = { r & foo: r.fo }
@ -2026,7 +2026,7 @@ mod test_reporting {
This is usually a typo. Here are the `r` fields that are most similar:
{ fo : Int
{ fo : I64
}ext
So maybe `.foo` should be `.fo`?
@ -2259,12 +2259,12 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
Either : [ Left Int, Right Bool ]
Either : [ Left I64, Right Bool ]
x : Either
x = Left 42
f : Either -> Int
f : Either -> I64
f = \Left v -> v
f x
@ -2296,7 +2296,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
x : [ Left Int, Right Bool ]
x : [ Left I64, Right Bool ]
x = Left 42
@ -2425,7 +2425,7 @@ mod test_reporting {
r#"
RemoteData e a : [ NotAsked, Loading, Failure e, Success a ]
x : RemoteData Int Str
x : RemoteData I64 Str
when x is
NotAsked -> 3
@ -2488,7 +2488,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
y : [ Nothing, Just Int ]
y : [ Nothing, Just I64 ]
y = Just 4
x = { a: y, b: 42}
@ -2581,9 +2581,9 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
Foo : { x : Int }
Foo : { x : I64 }
f : Foo -> Int
f : Foo -> I64
f = \r -> r.x
f { y: 3.14 }
@ -2605,7 +2605,7 @@ mod test_reporting {
But `f` needs the 1st argument to be:
{ x : Int }
{ x : I64 }
Tip: Seems like a record field typo. Maybe `y` should be `x`?
@ -2835,7 +2835,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
a : { foo : Int, bar : F64, foo : Str }
a : { foo : I64, bar : F64, foo : Str }
a = { bar: 3.0, foo: "foo" }
a
@ -2847,12 +2847,12 @@ mod test_reporting {
This record type defines the `.foo` field twice!
1 a : { foo : Int, bar : F64, foo : Str }
1 a : { foo : I64, bar : F64, foo : Str }
^^^^^^^^^ ^^^^^^^^^
In the rest of the program, I will only use the latter definition:
1 a : { foo : Int, bar : F64, foo : Str }
1 a : { foo : I64, bar : F64, foo : Str }
^^^^^^^^^
For clarity, remove the previous `.foo` definitions from this record
@ -2867,7 +2867,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
a : [ Foo Int, Bar F64, Foo Str ]
a : [ Foo I64, Bar F64, Foo Str ]
a = Foo "foo"
a
@ -2879,12 +2879,12 @@ mod test_reporting {
This tag union type defines the `Foo` tag twice!
1 a : [ Foo Int, Bar F64, Foo Str ]
1 a : [ Foo I64, Bar F64, Foo Str ]
^^^^^^^ ^^^^^^^
In the rest of the program, I will only use the latter definition:
1 a : [ Foo Int, Bar F64, Foo Str ]
1 a : [ Foo I64, Bar F64, Foo Str ]
^^^^^^^
For clarity, remove the previous `Foo` definitions from this tag union
@ -2899,7 +2899,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
bar : Int
bar : I64
foo = \x -> x
# NOTE: neither bar or foo are defined at this point
@ -2913,7 +2913,7 @@ mod test_reporting {
This annotation does not match the definition immediately following
it:
1> bar : Int
1> bar : I64
2> foo = \x -> x
Is it a typo? If not, put either a newline or comment between them.
@ -2927,7 +2927,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
bar : Int
bar : I64
foo = \x -> x
@ -2943,7 +2943,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
MyAlias 1 : Int
MyAlias 1 : I64
4
"#
@ -2954,7 +2954,7 @@ mod test_reporting {
This pattern in the definition of `MyAlias` is not what I expect:
1 MyAlias 1 : Int
1 MyAlias 1 : I64
^
Only type variables like `a` or `value` can occur in this position.
@ -2963,7 +2963,7 @@ mod test_reporting {
`MyAlias` is not used anywhere in your code.
1 MyAlias 1 : Int
1 MyAlias 1 : I64
^^^^^^^^^^^^^^^
If you didn't intend on using `MyAlias` then remove it so future readers
@ -2978,7 +2978,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
a : Num Int F64
a : Num I64 F64
a = 3
a
@ -2990,7 +2990,7 @@ mod test_reporting {
The `Num` alias expects 1 type argument, but it got 2 instead:
1 a : Num Int F64
1 a : Num I64 F64
^^^^^^^^^^^
Are there missing parentheses?
@ -3004,7 +3004,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
f : Bool -> Num Int F64
f : Bool -> Num I64 F64
f = \_ -> 3
f
@ -3016,7 +3016,7 @@ mod test_reporting {
The `Num` alias expects 1 type argument, but it got 2 instead:
1 f : Bool -> Num Int F64
1 f : Bool -> Num I64 F64
^^^^^^^^^^^
Are there missing parentheses?
@ -3032,7 +3032,7 @@ mod test_reporting {
r#"
Pair a b : [ Pair a b ]
x : Pair Int
x : Pair I64
x = Pair 2 3
x
@ -3044,7 +3044,7 @@ mod test_reporting {
The `Pair` alias expects 2 type arguments, but it got 1 instead:
3 x : Pair Int
3 x : Pair I64
^^^^^^^^
Are there missing parentheses?
@ -3060,7 +3060,7 @@ mod test_reporting {
r#"
Pair a b : [ Pair a b ]
x : Pair Int Int Int
x : Pair I64 I64 I64
x = 3
x
@ -3072,7 +3072,7 @@ mod test_reporting {
The `Pair` alias expects 2 type arguments, but it got 3 instead:
3 x : Pair Int Int Int
3 x : Pair I64 I64 I64
^^^^^^^^^^^^^^^^
Are there missing parentheses?
@ -3088,7 +3088,7 @@ mod test_reporting {
r#"
Foo a : [ Foo ]
f : Foo Int
f : Foo I64
f
"#
@ -3176,7 +3176,7 @@ mod test_reporting {
AList a b : [ ACons a (BList a b), ANil ]
BList a b : [ BCons a (AList a b), BNil ]
x : AList Int Int
x : AList I64 I64
x = ACons 0 (BCons 1 (ACons "foo" BNil ))
y : BList a a
@ -3191,19 +3191,19 @@ mod test_reporting {
Something is off with the body of the `x` definition:
4 x : AList Int Int
4 x : AList I64 I64
5 x = ACons 0 (BCons 1 (ACons "foo" BNil ))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This `ACons` global tag application has the type:
[ ACons (Num Integer) [ BCons (Num Integer) [ ACons Str [
BCons Int [ ACons Int (BList Int Int), ANil ] as a, BNil ], ANil
BCons I64 [ ACons I64 (BList I64 I64), ANil ] as a, BNil ], ANil
], BNil ], ANil ]
But the type annotation on `x` says it should be:
[ ACons Int (BList Int Int), ANil ] as a
[ ACons I64 (BList I64 I64), ANil ] as a
"#
),
)
@ -3575,7 +3575,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
f : { x : Int, y ? Int } -> Int
f : { x : I64, y ? I64 } -> I64
f = \{ x, y ? "foo" } -> (\g, _ -> g) x y
f
@ -3592,11 +3592,11 @@ mod test_reporting {
The argument is a pattern that matches record values of type:
{ x : Int, y ? Str }
{ x : I64, y ? Str }
But the annotation on `f` says the 1st argument should be:
{ x : Int, y ? Int }
{ x : I64, y ? I64 }
"#
),
)
@ -3608,7 +3608,7 @@ mod test_reporting {
indoc!(
r#"
\rec ->
{ x, y } : { x : Int, y ? Bool }
{ x, y } : { x : I64, y ? Bool }
{ x, y } = rec
{ x, y }
@ -3620,16 +3620,16 @@ mod test_reporting {
Something is off with the body of this definition:
2> { x, y } : { x : Int, y ? Bool }
2> { x, y } : { x : I64, y ? Bool }
3> { x, y } = rec
The body is a value of type:
{ x : Int, y : Bool }
{ x : I64, y : Bool }
But the type annotation says it should be:
{ x : Int, y ? Bool }
{ x : I64, y ? Bool }
Tip: To extract the `.y` field it must be non-optional, but the type
says this field is optional. Learn more about optional fields at TODO.
@ -3643,7 +3643,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
f : { x : Int, y ? Int } -> Int
f : { x : I64, y ? I64 } -> I64
f = \{ x, y } -> x + y
f
@ -3660,11 +3660,11 @@ mod test_reporting {
The argument is a pattern that matches record values of type:
{ x : Int, y : Int }
{ x : I64, y : I64 }
But the annotation on `f` says the 1st argument should be:
{ x : Int, y ? Int }
{ x : I64, y ? I64 }
Tip: To extract the `.y` field it must be non-optional, but the type
says this field is optional. Learn more about optional fields at TODO.
@ -3678,7 +3678,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
f : { x : Int, y ? Int } -> Int
f : { x : I64, y ? I64 } -> I64
f = \r ->
when r is
{ x, y } -> x + y
@ -3697,11 +3697,11 @@ mod test_reporting {
The first pattern is trying to match record values of type:
{ x : Int, y : Int }
{ x : I64, y : I64 }
But the expression between `when` and `is` has the type:
{ x : Int, y ? Int }
{ x : I64, y ? I64 }
Tip: To extract the `.y` field it must be non-optional, but the type
says this field is optional. Learn more about optional fields at TODO.
@ -3715,7 +3715,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
f : { x : Int, y ? Int } -> Int
f : { x : I64, y ? I64 } -> I64
f = \r -> r.y
f
@ -3732,11 +3732,11 @@ mod test_reporting {
This `r` value is a:
{ x : Int, y ? Int }
{ x : I64, y ? I64 }
But you are trying to use it as:
{ x : Int, y : Int }
{ x : I64, y : I64 }
Tip: To extract the `.y` field it must be non-optional, but the type
says this field is optional. Learn more about optional fields at TODO.
@ -3750,7 +3750,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
f : { x : Int, y ? Int } -> Int
f : { x : I64, y ? I64 } -> I64
f = \r -> .y r
f
@ -3767,11 +3767,11 @@ mod test_reporting {
This `r` value is a:
{ x : Int, y ? Int }
{ x : I64, y ? I64 }
But this function needs the 1st argument to be:
{ x : Int, y : Int }
{ x : I64, y : I64 }
Tip: To extract the `.y` field it must be non-optional, but the type
says this field is optional. Learn more about optional fields at TODO.
@ -3785,7 +3785,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
f : { x : Int, y : Int } -> Int
f : { x : I64, y : I64 } -> I64
f = \r ->
when r is
{ x, y : "foo" } -> x + 0
@ -3805,11 +3805,11 @@ mod test_reporting {
The first pattern is trying to match record values of type:
{ x : Int, y : Str }
{ x : I64, y : Str }
But the expression between `when` and `is` has the type:
{ x : Int, y : Int }
{ x : I64, y : I64 }
"#
),
)
@ -3820,7 +3820,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
f : { x : Int, y ? Int } -> Int
f : { x : I64, y ? I64 } -> I64
f = \r ->
when r is
{ x, y ? "foo" } -> (\g, _ -> g) x y
@ -3840,11 +3840,11 @@ mod test_reporting {
The first pattern is trying to match record values of type:
{ x : Int, y ? Str }
{ x : I64, y ? Str }
But the expression between `when` and `is` has the type:
{ x : Int, y ? Int }
{ x : I64, y ? I64 }
"#
),
)

View file

@ -1326,12 +1326,12 @@ mod solve_expr {
infer_eq(
indoc!(
r#"
int : Int
int : I64
int
"#
),
"Int",
"I64",
);
}
@ -1345,7 +1345,7 @@ mod solve_expr {
int
"#
),
"Int",
"I64",
);
}
@ -1354,13 +1354,13 @@ mod solve_expr {
infer_eq(
indoc!(
r#"
int : Int
int : I64
int = 5
int
"#
),
"Int",
"I64",
);
}
@ -1369,13 +1369,13 @@ mod solve_expr {
infer_eq(
indoc!(
r#"
int : Num.Int
int : Num.I64
int = 5
int
"#
),
"Int",
"I64",
);
}
@ -1390,7 +1390,7 @@ mod solve_expr {
int
"#
),
"Int",
"I64",
);
}
@ -1405,7 +1405,7 @@ mod solve_expr {
int
"#
),
"Int",
"I64",
);
}
@ -1489,13 +1489,13 @@ mod solve_expr {
r#"
Res a e : [ Okay a, Error e ]
ok : Res Int *
ok : Res I64 *
ok = Okay 5
ok
"#
),
"Res Int *",
"Res I64 *",
);
}
@ -1521,13 +1521,13 @@ mod solve_expr {
infer_eq(
indoc!(
r#"
ok : Result Int *
ok : Result I64 *
ok = Ok 5
ok
"#
),
"Result Int *",
"Result I64 *",
);
}
@ -1551,7 +1551,7 @@ mod solve_expr {
infer_eq(
indoc!(
r#"
ok : Result Int *
ok : Result I64 *
ok = Ok 5
err : Result * Str
@ -1563,7 +1563,7 @@ mod solve_expr {
err
"#
),
"Result Int Str",
"Result I64 Str",
);
}
@ -1584,19 +1584,19 @@ mod solve_expr {
// #[test]
// fn annotation_using_num_used() {
// // There was a problem where `Int`, because it is only an annotation
// // There was a problem where `I64`, because it is only an annotation
// // wasn't added to the vars_by_symbol.
// infer_eq_without_problem(
// indoc!(
// r#"
// int : Int
// int : I64
// p = (\x -> x) int
// p
// "#
// ),
// "Int",
// "I64",
// );
// }
@ -1631,7 +1631,7 @@ mod solve_expr {
x
"#
),
"Int",
"I64",
);
}
@ -1840,7 +1840,7 @@ mod solve_expr {
v
"#
),
"Foo Int",
"Foo I64",
);
}
@ -1907,7 +1907,7 @@ mod solve_expr {
length
"#
),
"Peano -> Int",
"Peano -> I64",
);
}
@ -2253,13 +2253,13 @@ mod solve_expr {
// infer_eq_without_problem(
// indoc!(
// r#"
// UserId x : [ UserId Int ]
// UserId x : [ UserId I64 ]
// UserId x = UserId 42
// x
// "#
// ),
// "Int",
// "I64",
// );
// }
@ -2340,13 +2340,13 @@ mod solve_expr {
ListB a : [ Cons a (ListC a) ]
ListC a : [ Cons a (ListA a), Nil ]
val : ListC Num.Int
val : ListC Num.I64
val = Cons 1 (Cons 2 (Cons 3 Nil))
val
"#
),
"ListC Int",
"ListC I64",
);
}
@ -2388,7 +2388,7 @@ mod solve_expr {
infer_eq_without_problem(
indoc!(
r#"
partition : Int, Int, List Int -> [ Pair Int (List Int) ]
partition : I64, I64, List I64 -> [ Pair I64 (List I64) ]
partition = \low, high, initialList ->
when List.get initialList high is
Ok _ ->
@ -2400,7 +2400,7 @@ mod solve_expr {
partition
"#
),
"Int, Int, List Int -> [ Pair Int (List Int) ]",
"I64, I64, List I64 -> [ Pair I64 (List I64) ]",
);
}
@ -2410,7 +2410,7 @@ mod solve_expr {
infer_eq_without_problem(
indoc!(
r#"
swap : Int, Int, List a -> List a
swap : I64, I64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -2421,7 +2421,7 @@ mod solve_expr {
_ ->
list
partition : Int, Int, List Int -> [ Pair Int (List Int) ]
partition : I64, I64, List I64 -> [ Pair I64 (List I64) ]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -2449,7 +2449,7 @@ mod solve_expr {
partition
"#
),
"Int, Int, List Int -> [ Pair Int (List Int) ]",
"I64, I64, List I64 -> [ Pair I64 (List I64) ]",
);
});
}
@ -2462,14 +2462,14 @@ mod solve_expr {
idList : List a -> List a
idList = \list -> list
foo : List Int -> List Int
foo : List I64 -> List I64
foo = \initialList -> idList initialList
foo
"#
),
"List Int -> List Int",
"List I64 -> List I64",
);
}
@ -2547,7 +2547,7 @@ mod solve_expr {
Num.ceiling
"#
),
"F64 -> Int",
"F64 -> I64",
);
}
@ -2559,7 +2559,7 @@ mod solve_expr {
Num.floor
"#
),
"F64 -> Int",
"F64 -> I64",
);
}
@ -2571,7 +2571,7 @@ mod solve_expr {
Num.powInt
"#
),
"Int, Int -> Int",
"I64, I64 -> I64",
);
}
@ -2789,15 +2789,15 @@ mod solve_expr {
#[should_panic]
fn rigid_record_quantification() {
// the ext here is qualified on the outside (because we have rank 1 types, not rank 2).
// That means e.g. `f : { bar : String, foo : Int } -> Bool }` is a valid argument, but
// that function could not be applied to the `{ foo : Int }` list. Therefore, this function
// That means e.g. `f : { bar : String, foo : I64 } -> Bool }` is a valid argument, but
// that function could not be applied to the `{ foo : I64 }` list. Therefore, this function
// is not allowed.
//
// should hit a debug_assert! in debug mode, and produce a type error in release mode
infer_eq_without_problem(
indoc!(
r#"
test : ({ foo : Int }ext -> Bool), { foo : Int } -> Bool
test : ({ foo : I64 }ext -> Bool), { foo : I64 } -> Bool
test = \fn, a -> fn a
test
@ -2814,12 +2814,12 @@ mod solve_expr {
infer_eq_without_problem(
indoc!(
r#"
negatePoint : { x : Int, y : Int, z ? Num c } -> { x : Int, y : Int, z : Num c }
negatePoint : { x : I64, y : I64, z ? Num c } -> { x : I64, y : I64, z : Num c }
negatePoint { x: 1, y: 2 }
"#
),
"{ x : Int, y : Int, z : Num c }",
"{ x : I64, y : I64, z : Num c }",
);
}
@ -2828,7 +2828,7 @@ mod solve_expr {
infer_eq_without_problem(
indoc!(
r#"
negatePoint : { x : Int, y : Int, z ? Num c }r -> { x : Int, y : Int, z : Num c }r
negatePoint : { x : I64, y : I64, z ? Num c }r -> { x : I64, y : I64, z : Num c }r
a = negatePoint { x: 1, y: 2 }
b = negatePoint { x: 1, y: 2, blah : "hi" }
@ -2836,7 +2836,7 @@ mod solve_expr {
{ a, b }
"#
),
"{ a : { x : Int, y : Int, z : Num c }, b : { blah : Str, x : Int, y : Int, z : Num c } }",
"{ a : { x : I64, y : I64, z : Num c }, b : { blah : Str, x : I64, y : I64, z : Num c } }",
);
}
@ -2850,7 +2850,7 @@ mod solve_expr {
negatePoint { x: 1, y: 2.1, z: 0x3 }
"#
),
"{ x : Num a, y : F64, z : Int }",
"{ x : Num a, y : F64, z : I64 }",
);
}
@ -2917,13 +2917,13 @@ mod solve_expr {
indoc!(
r#"
\rec ->
{ x, y } : { x : Int, y ? Bool }*
{ x, y } : { x : I64, y ? Bool }*
{ x, y ? False } = rec
{ x, y }
"#
),
"{ x : Int, y ? Bool }* -> { x : Int, y : Bool }",
"{ x : I64, y ? Bool }* -> { x : I64, y : Bool }",
);
}
@ -2944,14 +2944,14 @@ mod solve_expr {
infer_eq_without_problem(
indoc!(
r#"
empty : List Int
empty : List I64
empty =
[]
List.walkBackwards empty (\a, b -> a + b) 0
"#
),
"Int",
"I64",
);
}
@ -2970,7 +2970,7 @@ mod solve_expr {
g
"#
),
"Int",
"I64",
);
}
@ -3023,7 +3023,7 @@ mod solve_expr {
app "test" provides [ main ] to "./platform"
Bar : [ Bar ]
Foo : [ Foo Bar Int, Empty ]
Foo : [ Foo Bar I64, Empty ]
foo : Foo
foo = Foo Bar 1
@ -3037,7 +3037,7 @@ mod solve_expr {
x
"#
),
"[ Empty, Foo [ Bar ] Int ]",
"[ Empty, Foo [ Bar ] I64 ]",
);
}
@ -3048,7 +3048,7 @@ mod solve_expr {
r#"
app "test" provides [ main ] to "./platform"
Foo : [ @Foo [ @Bar ] Int, @Empty ]
Foo : [ @Foo [ @Bar ] I64, @Empty ]
foo : Foo
foo = @Foo @Bar 1
@ -3062,7 +3062,7 @@ mod solve_expr {
x
"#
),
"[ @Empty, @Foo [ @Bar ] Int ]",
"[ @Empty, @Foo [ @Bar ] I64 ]",
);
}
@ -3073,21 +3073,21 @@ mod solve_expr {
r#"
app "test" provides [ main ] to "./platform"
State a : { count : Int, x : a }
State a : { count : I64, x : a }
foo : State a -> Int
foo : State a -> I64
foo = \state ->
if state.count == 0 then
0
else
1 + foo { count: state.count - 1, x: state.x }
main : Int
main : I64
main =
foo { count: 3, x: {} }
"#
),
"Int",
"I64",
);
}
@ -3108,15 +3108,15 @@ mod solve_expr {
empty =
Empty
foo : Dict Int Int
foo : Dict I64 I64
foo = empty
main : Dict Int Int
main : Dict I64 I64
main =
foo
"#
),
"Dict Int Int",
"Dict I64 I64",
);
}
@ -3370,12 +3370,12 @@ mod solve_expr {
removeHelpEQGT targetKey (removeHelpPrepEQGT targetKey dict color key value left right)
main : Dict Int Int
main : Dict I64 I64
main =
removeHelp 1 Empty
"#
),
"Dict Int Int",
"Dict I64 I64",
);
}
@ -3410,12 +3410,12 @@ mod solve_expr {
Empty
main : Dict Int
main : Dict I64
main =
removeHelp 1 Empty
"#
),
"Dict Int",
"Dict I64",
);
}
@ -3486,12 +3486,12 @@ mod solve_expr {
removeMin : Dict k v -> Dict k v
main : Dict Int Int
main : Dict I64 I64
main =
removeHelp 1 Empty
"#
),
"Dict Int Int",
"Dict I64 I64",
);
}
@ -3502,7 +3502,7 @@ mod solve_expr {
r#"
app "test" provides [ partitionHelp ] to "./platform"
swap : Int, Int, List a -> List a
swap : I64, I64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -3513,7 +3513,7 @@ mod solve_expr {
_ ->
[]
partitionHelp : Int, Int, List (Num a), Int, (Num a) -> [ Pair Int (List (Num a)) ]
partitionHelp : I64, I64, List (Num a), I64, (Num a) -> [ Pair I64 (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is
@ -3529,7 +3529,7 @@ mod solve_expr {
Pair i list
"#
),
"Int, Int, List (Num a), Int, Num a -> [ Pair Int (List (Num a)) ]",
"I64, I64, List (Num a), I64, Num a -> [ Pair I64 (List (Num a)) ]",
);
}
@ -3546,12 +3546,12 @@ mod solve_expr {
balance = \key, left ->
Node key left Empty
main : Dict Int
main : Dict I64
main =
balance 0 Empty
"#
),
"Dict Int",
"Dict I64",
);
}
@ -3570,12 +3570,12 @@ mod solve_expr {
balance = \key, left ->
node key left Empty
main : Dict Int
main : Dict I64
main =
balance 0 Empty
"#
),
"Dict Int",
"Dict I64",
);
}
@ -3619,12 +3619,12 @@ mod solve_expr {
_ ->
Node color key value left right
main : Dict Int Int
main : Dict I64 I64
main =
balance Red 0 0 Empty Empty
"#
),
"Dict Int Int",
"Dict I64 I64",
);
}
@ -3648,12 +3648,12 @@ mod solve_expr {
Empty
main : Dict Int
main : Dict I64
main =
balance 0 Empty
"#
),
"Dict Int",
"Dict I64",
);
}
}

View file

@ -1124,7 +1124,7 @@ mod solve_uniq_expr {
x
"#
),
"Attr * Int",
"Attr * I64",
);
}
@ -1377,7 +1377,7 @@ mod solve_uniq_expr {
x
"#
),
"Attr * Int",
"Attr * I64",
);
}
@ -1405,7 +1405,7 @@ mod solve_uniq_expr {
infer_eq(
indoc!(
r#"
swap : Int, Int, List a -> List a
swap : I64, I64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -1418,7 +1418,7 @@ mod solve_uniq_expr {
swap
"#
),
"Attr * (Attr * Int, Attr * Int, Attr * (List (Attr Shared a)) -> Attr * (List (Attr Shared a)))"
"Attr * (Attr * I64, Attr * I64, Attr * (List (Attr Shared a)) -> Attr * (List (Attr Shared a)))"
);
}
@ -1431,7 +1431,7 @@ mod solve_uniq_expr {
infer_eq(
indoc!(
r#"
quicksort : List (Num a), Int, Int -> List (Num a)
quicksort : List (Num a), I64, I64 -> List (Num a)
quicksort = \list, low, high ->
when partition low high list is
Pair partitionIndex partitioned ->
@ -1440,7 +1440,7 @@ mod solve_uniq_expr {
|> quicksort (partitionIndex + 1) high
swap : Int, Int, List a -> List a
swap : I64, I64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -1452,7 +1452,7 @@ mod solve_uniq_expr {
[]
partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -1480,7 +1480,7 @@ mod solve_uniq_expr {
quicksort
"#
),
"Attr Shared (Attr b (List (Attr Shared (Num (Attr Shared a)))), Attr Shared Int, Attr Shared Int -> Attr b (List (Attr Shared (Num (Attr Shared a)))))"
"Attr Shared (Attr b (List (Attr Shared (Num (Attr Shared a)))), Attr Shared I64, Attr Shared I64 -> Attr b (List (Attr Shared (Num (Attr Shared a)))))"
);
})
}
@ -2149,7 +2149,7 @@ mod solve_uniq_expr {
Num.maxInt // Num.maxInt
"#
),
"Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))",
"Attr * (Result (Attr * I64) (Attr * [ DivByZero ]*))",
);
}
@ -2161,7 +2161,7 @@ mod solve_uniq_expr {
3 // 4
"#
),
"Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))",
"Attr * (Result (Attr * I64) (Attr * [ DivByZero ]*))",
);
}
@ -2173,7 +2173,7 @@ mod solve_uniq_expr {
3 // Num.maxInt
"#
),
"Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))",
"Attr * (Result (Attr * I64) (Attr * [ DivByZero ]*))",
);
}
@ -2264,17 +2264,17 @@ mod solve_uniq_expr {
#[test]
fn list_len() {
infer_eq("List.len", "Attr * (Attr * (List *) -> Attr * Int)");
infer_eq("List.len", "Attr * (Attr * (List *) -> Attr * I64)");
}
#[test]
fn list_get() {
infer_eq("List.get", "Attr * (Attr (* | b) (List (Attr b a)), Attr * Int -> Attr * (Result (Attr b a) (Attr * [ OutOfBounds ]*)))");
infer_eq("List.get", "Attr * (Attr (* | b) (List (Attr b a)), Attr * I64 -> Attr * (Result (Attr b a) (Attr * [ OutOfBounds ]*)))");
}
#[test]
fn list_set() {
infer_eq("List.set", "Attr * (Attr (* | b | c) (List (Attr b a)), Attr * Int, Attr (b | c) a -> Attr * (List (Attr b a)))");
infer_eq("List.set", "Attr * (Attr (* | b | c) (List (Attr b a)), Attr * I64, Attr (b | c) a -> Attr * (List (Attr b a)))");
}
#[test]
@ -2286,7 +2286,7 @@ mod solve_uniq_expr {
fn list_repeat() {
infer_eq(
"List.repeat",
"Attr * (Attr * Int, Attr Shared a -> Attr * (List (Attr Shared a)))",
"Attr * (Attr * I64, Attr Shared a -> Attr * (List (Attr Shared a)))",
);
}
@ -2487,15 +2487,15 @@ mod solve_uniq_expr {
infer_eq(
indoc!(
r#"
Model : { foo : Int }
Model : { foo : I64 }
extract : Model -> Int
extract : Model -> I64
extract = \{ foo } -> foo
extract
"#
),
"Attr * (Attr (* | a) Model -> Attr a Int)",
"Attr * (Attr (* | a) Model -> Attr a I64)",
);
}
@ -2504,15 +2504,15 @@ mod solve_uniq_expr {
infer_eq(
indoc!(
r#"
Model : { foo : Int, bar : Int }
Model : { foo : I64, bar : I64 }
extract : Model -> Int
extract : Model -> I64
extract = \{ foo } -> foo
extract
"#
),
"Attr * (Attr (* | a) Model -> Attr a Int)",
"Attr * (Attr (* | a) Model -> Attr a I64)",
);
}
@ -2521,15 +2521,15 @@ mod solve_uniq_expr {
infer_eq(
indoc!(
r#"
Model : { foo : Int, bar : Int }
Model : { foo : I64, bar : I64 }
extract : Model -> Int
extract : Model -> I64
extract = \{foo, bar} -> foo + bar
extract
"#
),
"Attr * (Attr (* | * | *) Model -> Attr * Int)",
"Attr * (Attr (* | * | *) Model -> Attr * I64)",
);
}
@ -2627,13 +2627,13 @@ mod solve_uniq_expr {
infer_eq(
indoc!(
r#"
f : Int, Int -> Int
f : I64, I64 -> I64
f = \a, b -> a + b
f
"#
),
"Attr * (Attr * Int, Attr * Int -> Attr * Int)",
"Attr * (Attr * I64, Attr * I64 -> Attr * I64)",
);
}
@ -2642,13 +2642,13 @@ mod solve_uniq_expr {
infer_eq(
indoc!(
r#"
foobar : Int -> Int
foobar : I64 -> I64
foobar = \x -> Num.abs x
foobar
"#
),
"Attr * (Attr * Int -> Attr * Int)",
"Attr * (Attr * I64 -> Attr * I64)",
);
}
@ -2662,7 +2662,7 @@ mod solve_uniq_expr {
f
"#
),
"Attr * (Attr a Int, Attr b Int -> Attr c Int)",
"Attr * (Attr a I64, Attr b I64 -> Attr c I64)",
);
}
@ -3001,13 +3001,13 @@ mod solve_uniq_expr {
Model a : { foo : Set a }
initialModel : position -> Model Int
initialModel : position -> Model I64
initialModel = \_ -> { foo : Set.empty }
initialModel
"#
),
"Attr * (Attr * position -> Attr * (Model (Attr * Int)))",
"Attr * (Attr * position -> Attr * (Model (Attr * I64)))",
);
}
@ -3035,12 +3035,12 @@ mod solve_uniq_expr {
infer_eq(
indoc!(
r#"
negatePoint : { x : Int, y : Int, z ? Num c } -> { x : Int, y : Int, z : Num c }
negatePoint : { x : I64, y : I64, z ? Num c } -> { x : I64, y : I64, z : Num c }
negatePoint { x: 1, y: 2 }
"#
),
"Attr * { x : Attr * Int, y : Attr * Int, z : Attr * (Num (Attr * c)) }",
"Attr * { x : Attr * I64, y : Attr * I64, z : Attr * (Num (Attr * c)) }",
);
}
@ -3049,7 +3049,7 @@ mod solve_uniq_expr {
infer_eq(
indoc!(
r#"
negatePoint : { x : Int, y : Int, z ? Num c }r -> { x : Int, y : Int, z : Num c }r
negatePoint : { x : I64, y : I64, z ? Num c }r -> { x : I64, y : I64, z : Num c }r
a = negatePoint { x: 1, y: 2 }
b = negatePoint { x: 1, y: 2, blah : "hi" }
@ -3057,7 +3057,7 @@ mod solve_uniq_expr {
{ a, b }
"#
),
"Attr * { a : Attr * { x : Attr * Int, y : Attr * Int, z : Attr * (Num (Attr * c)) }, b : Attr * { blah : Attr * Str, x : Attr * Int, y : Attr * Int, z : Attr * (Num (Attr * c)) } }"
"Attr * { a : Attr * { x : Attr * I64, y : Attr * I64, z : Attr * (Num (Attr * c)) }, b : Attr * { blah : Attr * Str, x : Attr * I64, y : Attr * I64, z : Attr * (Num (Attr * c)) } }"
);
}
@ -3071,7 +3071,7 @@ mod solve_uniq_expr {
negatePoint { x: 1, y: 2.1, z: 0x3 }
"#
),
"Attr * { x : Attr * (Num (Attr * a)), y : Attr * F64, z : Attr * Int }",
"Attr * { x : Attr * (Num (Attr * a)), y : Attr * F64, z : Attr * I64 }",
);
}
@ -3149,14 +3149,14 @@ mod solve_uniq_expr {
infer_eq(
indoc!(
r#"
empty : List Int
empty : List I64
empty =
[]
List.walkBackwards empty (\a, b -> a + b) 0
"#
),
"Attr a Int",
"Attr a I64",
);
}
}

View file

@ -50,7 +50,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
// Int : Num Integer
add_alias(
Symbol::NUM_INT,
Symbol::NUM_I64,
BuiltinAlias {
region: Region::zero(),
vars: Vec::new(),
@ -155,7 +155,7 @@ fn float_alias_content() -> SolvedType {
#[inline(always)]
pub fn int_type() -> SolvedType {
SolvedType::Alias(Symbol::NUM_INT, Vec::new(), Box::new(int_alias_content()))
SolvedType::Alias(Symbol::NUM_I64, Vec::new(), Box::new(int_alias_content()))
}
#[inline(always)]

View file

@ -19,8 +19,8 @@ static EMPTY_TAG_UNION: &str = "[]";
///
/// Separately, if we're inside a type parameter, we may need to use parens:
///
/// List Int
/// List (List Int)
/// List I64
/// List (List I64)
///
/// Otherwise, parens are unnecessary.
#[derive(Clone, Copy, Debug, PartialEq)]
@ -330,7 +330,7 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par
match &content {
Alias(nested, _, _) => match *nested {
Symbol::NUM_INTEGER => buf.push_str("Int"),
Symbol::NUM_INTEGER => buf.push_str("I64"),
Symbol::NUM_FLOATINGPOINT => buf.push_str("F64"),
_ => write_parens!(write_parens, buf, {
@ -343,7 +343,7 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par
let attr_content = subs.get_without_compacting(nested_args[1]).content;
match &attr_content {
Alias(nested, _, _) => match *nested {
Symbol::NUM_INTEGER => buf.push_str("Int"),
Symbol::NUM_INTEGER => buf.push_str("I64"),
Symbol::NUM_FLOATINGPOINT => buf.push_str("F64"),
_ => write_parens!(write_parens, buf, {
buf.push_str("Num ");
@ -403,7 +403,7 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String
Record(fields, ext_var) => {
use crate::types::{gather_fields, RecordStructure};
// If the `ext` has concrete fields (e.g. { foo : Int}{ bar : Bool }), merge them
// If the `ext` has concrete fields (e.g. { foo : I64}{ bar : Bool }), merge them
let RecordStructure { fields, ext } = gather_fields(subs, fields, ext_var);
let ext_var = ext;
@ -465,8 +465,8 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String
// This is an open record, so print the variable
// right after the '}'
//
// e.g. the "*" at the end of `{ x: Int }*`
// or the "r" at the end of `{ x: Int }r`
// e.g. the "*" at the end of `{ x: I64 }*`
// or the "r" at the end of `{ x: I64 }r`
write_content(env, content, subs, buf, parens)
}
}
@ -523,8 +523,8 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String
// This is an open tag union, so print the variable
// right after the ']'
//
// e.g. the "*" at the end of `{ x: Int }*`
// or the "r" at the end of `{ x: Int }r`
// e.g. the "*" at the end of `{ x: I64 }*`
// or the "r" at the end of `{ x: I64 }r`
write_content(env, content, subs, buf, parens)
}
}
@ -576,8 +576,8 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String
// This is an open tag union, so print the variable
// right after the ']'
//
// e.g. the "*" at the end of `{ x: Int }*`
// or the "r" at the end of `{ x: Int }r`
// e.g. the "*" at the end of `{ x: I64 }*`
// or the "r" at the end of `{ x: I64 }r`
write_content(env, content, subs, buf, parens)
}
@ -754,7 +754,7 @@ fn write_apply(
match &arg_content {
Content::Structure(FlatType::Apply(symbol, nested_args)) => match *symbol {
Symbol::NUM_INTEGER if nested_args.is_empty() => {
buf.push_str("Int");
buf.push_str("I64");
}
Symbol::NUM_FLOATINGPOINT if nested_args.is_empty() => {
buf.push_str("F64");
@ -768,7 +768,7 @@ fn write_apply(
double_nested_args,
))) => match double_nested_symbol {
Symbol::NUM_INTEGER if double_nested_args.is_empty() => {
buf.push_str("Int");
buf.push_str("I64");
}
Symbol::NUM_FLOATINGPOINT if double_nested_args.is_empty() => {
buf.push_str("F64");

View file

@ -14,7 +14,7 @@ use roc_types::types::Type::{self, *};
pub fn int_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint {
let num_type = Variable(num_var);
let reason = Reason::IntLiteral;
let int_type = builtin_type(Symbol::NUM_INT, vec![]);
let int_type = builtin_type(Symbol::NUM_I64, vec![]);
let expected_literal = ForReason(reason, int_type, region);
exists(

View file

@ -23,13 +23,17 @@ roc_solve = { path = "../compiler/solve" }
roc_mono = { path = "../compiler/mono" }
roc_load = { path = "../compiler/load" }
roc_gen = { path = "../compiler/gen" }
roc_fmt = { path = "../compiler/fmt" }
roc_reporting = { path = "../compiler/reporting" }
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
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"] }
arraystring = "0.3.0"
libc = "0.2"
page_size = "0.4"
# 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
@ -57,7 +61,11 @@ zerocopy = "0.3"
env_logger = "0.7"
futures = "0.3"
wgpu_glyph = "0.10"
bytemuck = "1.4"
cgmath = "0.17.0"
[dependencies.bytemuck]
version = "1.4"
features = ["derive"]
[dev-dependencies]
pretty_assertions = "0.5.1"

File diff suppressed because it is too large Load diff

457
editor/src/bucket.rs Normal file
View file

@ -0,0 +1,457 @@
/// A bucket of 16-byte nodes. The node value 0 is reserved for the bucket's
/// use, and valid nodes may never have that value.
///
/// By design, each bucket is 4096 bytes large. When you make a bucket, it
/// uses mmap to reserve one anonymous memory page in which to store nodes.
/// Since nodes are 16 bytes, one bucket can store 256 nodes; you can access
/// a particular node by its BucketSlot, which is an opaque wrapper around a u8.
///
/// Buckets also use the node value 0 (all 0 bits) to mark slots as unoccupied.
/// This is important for performance.
use libc::{c_void, calloc, free, mmap, munmap, MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE};
use std::marker::PhantomData;
use std::mem::size_of;
use std::ptr::null;
pub const BUCKET_BYTES: usize = 4096;
#[derive(Debug)]
pub struct NodeId<T: Sized> {
pub bucket_id: BucketId<T>,
pub slot: BucketSlot<T>,
}
#[test]
fn size_of_node_id() {
assert_eq!(std::mem::size_of::<NodeId<()>>(), 4);
}
impl<T> Clone for NodeId<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for NodeId<T> {}
impl<T: Sized> NodeId<T> {
fn next_slot(&self) -> Self {
NodeId {
bucket_id: self.bucket_id,
slot: self.slot.increment(),
}
}
}
impl<T> PartialEq for NodeId<T> {
fn eq(&self, other: &Self) -> bool {
self.bucket_id == other.bucket_id && self.slot == other.slot
}
}
impl<T> Eq for NodeId<T> {}
#[derive(Debug)]
#[repr(transparent)]
pub struct BucketId<T: Sized> {
value: u16,
_phantom: PhantomData<T>,
}
#[test]
fn size_of_bucket_id() {
assert_eq!(std::mem::size_of::<BucketId<()>>(), 2);
}
impl<T> Clone for BucketId<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for BucketId<T> {}
impl<T> PartialEq for BucketId<T> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<T> Eq for BucketId<T> {}
impl<T: Sized> BucketId<T> {
fn from_u16(value: u16) -> Self {
BucketId {
value,
_phantom: PhantomData::default(),
}
}
}
#[derive(Debug)]
#[repr(transparent)]
pub struct BucketSlot<T: Sized> {
value: u8,
_phantom: PhantomData<T>,
}
#[test]
fn size_of_bucket_slot() {
assert_eq!(std::mem::size_of::<BucketSlot<()>>(), 1);
}
impl<T> Clone for BucketSlot<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for BucketSlot<T> {}
impl<T> PartialEq for BucketSlot<T> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<T> Eq for BucketSlot<T> {}
impl<T: Sized> BucketSlot<T> {
#[allow(dead_code)]
fn from_u8(value: u8) -> Self {
BucketSlot {
value,
_phantom: PhantomData::default(),
}
}
fn increment(&self) -> Self {
BucketSlot {
value: self.value + 1,
_phantom: PhantomData::default(),
}
}
}
pub struct Buckets {
buckets: Vec<Bucket>,
// free_1node_slots: Vec<NodeId<T>>,
}
impl Buckets {
// fn find_space_for(&mut self, nodes: u8) -> Result<BucketId<T>, ()> {}
pub fn add<T: Sized>(&mut self) -> Result<BucketId<T>, ()> {
let num_buckets = self.buckets.len();
if num_buckets <= u16::MAX as usize {
let bucket_id = BucketId::from_u16(num_buckets as u16);
let bucket = Bucket::default();
self.buckets.push(bucket);
Ok(bucket_id)
} else {
Err(())
}
}
fn get_unchecked<'a, T: Sized>(&'a self, node_id: NodeId<T>) -> &'a T {
unsafe {
self.buckets
.get(node_id.bucket_id.value as usize)
.unwrap()
.get_unchecked(node_id.slot.value)
}
}
pub fn get<'a, T: Sized>(&'a self, node_id: NodeId<T>) -> Option<&'a T> {
self.buckets
.get(node_id.bucket_id.value as usize)
.and_then(|bucket| bucket.get(node_id.slot))
}
}
struct Bucket {
#[allow(dead_code)]
next_unused_slot: u16,
first_slot: *mut [u8; 16],
}
impl Bucket {
/// If there's room left in the bucket, adds the item and returns
/// the slot where it was put. If there was no room left, returns Err(()).
#[allow(dead_code)]
pub fn add<T: Sized>(&mut self, node: T) -> Result<BucketSlot<T>, ()> {
// It's only safe to store this as a *const T if T is 16 bytes.
// This is designed to be used exclusively with 16-byte nodes!
debug_assert_eq!(size_of::<T>(), 16);
// Once next_unused_slot exceeds u8::MAX, we have no room left.
if self.next_unused_slot <= u8::MAX as u16 {
let chosen_slot = self.next_unused_slot as u8;
unsafe { self.put_unchecked(node, chosen_slot) };
self.next_unused_slot += 1;
Ok(BucketSlot::from_u8(chosen_slot))
} else {
// No room left!
Err(())
}
}
/// If the given slot is available, inserts the given node into it.
/// Otherwise, returns the node that was in the already-occupied slot.
#[allow(dead_code)]
pub fn insert<T: Sized>(&mut self, node: T, slot: BucketSlot<T>) -> Result<(), &T> {
// It's only safe to use this if T is 16 bytes.
// This is designed to be used exclusively with 16-byte nodes!
debug_assert_eq!(size_of::<T>(), 16);
let slot = slot.value;
unsafe {
if self.is_available(slot) {
self.put_unchecked(node, slot);
Ok(())
} else {
Err(self.get_unchecked(slot))
}
}
}
pub fn get<'a, T: Sized>(&'a self, slot: BucketSlot<T>) -> Option<&'a T> {
// It's only safe to store this as a *const T if T is 16 bytes.
// This is designed to be used exclusively with 16-byte nodes!
debug_assert_eq!(size_of::<T>(), 16);
unsafe {
let slot_ptr = self.first_slot.offset(slot.value as isize) as *const T;
let value: &[u8; 16] = &*(slot_ptr as *const [u8; 16]);
if *value != [0; 16] {
Some(&*(value as *const [u8; 16] as *const T))
} else {
None
}
}
}
unsafe fn put_unchecked<T: Sized>(&mut self, node: T, slot: u8) {
// It's only safe to store this as a *const T if T is 16 bytes.
// This is designed to be used exclusively with 16-byte nodes!
debug_assert_eq!(size_of::<T>(), 16);
let slot_ptr = self.first_slot.offset(slot as isize) as *mut T;
*slot_ptr = node;
}
unsafe fn get_unchecked<T>(&self, slot: u8) -> &T {
&*(self.first_slot.offset(slot as isize) as *const T)
}
// A slot is available iff its bytes are all zeroes
unsafe fn is_available(&self, slot: u8) -> bool {
let slot_ptr = self.first_slot.offset(slot as isize) as *const [u8; 16];
*slot_ptr == [0; 16]
}
}
impl Default for Bucket {
fn default() -> Self {
let first_slot = if page_size::get() == 4096 {
unsafe {
// mmap exactly one memory page (4096 bytes)
mmap(
null::<c_void>() as *mut c_void,
BUCKET_BYTES,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
0,
0,
)
}
} else {
// Somehow the page size is not 4096 bytes, so fall back on calloc.
// (We use calloc over malloc because we rely on the bytes having
// been zeroed to tell which slots are available.)
unsafe { calloc(1, BUCKET_BYTES) }
} as *mut [u8; 16];
Bucket {
next_unused_slot: 0,
first_slot,
}
}
}
impl Drop for Bucket {
fn drop(&mut self) {
if page_size::get() == 4096 {
unsafe {
munmap(self.first_slot as *mut c_void, BUCKET_BYTES);
}
} else {
unsafe {
free(self.first_slot as *mut c_void);
}
}
}
}
#[derive(Debug)]
pub struct BucketStr {
first_node_id: NodeId<()>,
first_segment_len: u8,
}
#[test]
fn size_of_bucket_str() {
assert_eq!(std::mem::size_of::<BucketList<()>>(), 4);
}
/// A non-empty list inside a bucket. It takes 4B of memory.
///
/// This is internally represented as an array of at most 255 nodes, which
/// can grow to 256+ nodes by having the last nodeent be a linked list Cons
/// cell which points to another such backing array which has more nodes.
///
/// In practice, these will almost be far below 256 nodes, but in theory
/// they can be enormous in length thanks to the linked list fallback.
///
/// Since these are non-empty lists, we need separate variants for collections
/// that can be empty, e.g. EmptyRecord and EmptyList. In contrast, we don't
/// need an EmptyList or EmptyWhen, since although those use BucketList
/// to store their branches, having zero branches is syntactically invalid.
/// Same with Call and Closure, since all functions must have 1+ arguments.
#[derive(Debug)]
pub struct BucketList<T: Sized> {
first_node_id: BucketId<T>,
first_node_sl: BucketSlot<T>,
first_segment_len: u8,
}
#[test]
fn size_of_bucket_list() {
assert_eq!(std::mem::size_of::<BucketList<()>>(), 4);
}
impl<'a, T: 'a + Sized> BucketList<T> {
/// If given a first_segment_len of 0, that means this is a BucketList
/// consisting of 256+ nodes. The first 255 are stored in the usual
/// array, and then there's one more nodeent at the end which continues
/// the list with a new length and NodeId value. BucketList iterators
/// automatically do these jumps behind the scenes when necessary.
pub fn new(first_node_id: NodeId<T>, first_segment_len: u8) -> Self {
BucketList {
first_segment_len,
first_node_id: first_node_id.bucket_id,
first_node_sl: first_node_id.slot,
}
}
pub fn into_iter(self, buckets: &'a Buckets) -> impl Iterator<Item = &'a T> {
self.bucket_list_iter(buckets)
}
/// Private version of into_iter which exposes the implementation detail
/// of BucketListIter. We don't want that struct to be public, but we
/// actually do want to have this separate function for code reuse
/// in the iterator's next() method.
fn bucket_list_iter(&self, buckets: &'a Buckets) -> BucketListIter<'a, T> {
let first_segment_len = self.first_segment_len;
let continues_with_cons = first_segment_len == 0;
let len_remaining = if continues_with_cons {
// We have 255 nodes followed by a Cons cell continuing the list.
u8::MAX
} else {
first_segment_len
};
BucketListIter {
continues_with_cons,
len_remaining,
bucket_id: self.first_node_id,
slot: self.first_node_sl,
buckets,
}
}
}
struct BucketListIter<'a, T: Sized> {
bucket_id: BucketId<T>,
slot: BucketSlot<T>,
len_remaining: u8,
continues_with_cons: bool,
buckets: &'a Buckets,
}
impl<'a, T: Sized> Iterator for BucketListIter<'a, T>
where
T: 'a,
{
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
match self.len_remaining {
0 => match self.continues_with_cons {
// We're done! This is by far the most common case, so we put
// it first to avoid branch mispredictions.
false => None,
// We need to continue with a Cons cell.
true => {
let node_id = NodeId {
bucket_id: self.bucket_id,
slot: self.slot,
}
.next_slot();
// Since we have continues_with_cons set, the next slot
// will definitely be occupied with a BucketList struct.
let node = self.buckets.get_unchecked(node_id);
let next_list = unsafe { &*(node as *const T as *const BucketList<T>) };
// Replace the current iterator with an iterator into that
// list, and then continue with next() on that iterator.
let next_iter = next_list.bucket_list_iter(self.buckets);
self.bucket_id = next_iter.bucket_id;
self.slot = next_iter.slot;
self.len_remaining = next_iter.len_remaining;
self.continues_with_cons = next_iter.continues_with_cons;
self.next()
}
},
1 => {
self.len_remaining = 0;
// Don't advance the node pointer's slot, because that might
// advance past the end of the bucket!
Some(self.buckets.get_unchecked(NodeId {
bucket_id: self.bucket_id,
slot: self.slot,
}))
}
len_remaining => {
// Get the current node
let node_id = NodeId {
bucket_id: self.bucket_id,
slot: self.slot,
};
let node = self.buckets.get_unchecked(node_id);
// Advance the node pointer to the next slot in the current bucket
self.slot = self.slot.increment();
self.len_remaining = len_remaining - 1;
Some(node)
}
}
}
}

166
editor/src/buffer.rs Normal file
View file

@ -0,0 +1,166 @@
// Adapted from https://github.com/sotrh/learn-wgpu
// by Benjamin Hansen, licensed under the MIT license
use crate::rect::Rect;
use crate::util::size_of_slice;
use crate::vertex::Vertex;
use wgpu::util::{BufferInitDescriptor, DeviceExt};
pub struct QuadBufferBuilder {
vertex_data: Vec<Vertex>,
index_data: Vec<u32>,
current_quad: u32,
}
impl QuadBufferBuilder {
pub fn new() -> Self {
Self {
vertex_data: Vec::new(),
index_data: Vec::new(),
current_quad: 0,
}
}
pub fn push_rect(self, rect: &Rect) -> Self {
let coords = rect.top_left_coords;
self.push_quad(
coords.x,
coords.y - rect.height,
coords.x + rect.width,
coords.y,
rect.color,
)
}
pub fn push_quad(
mut self,
min_x: f32,
min_y: f32,
max_x: f32,
max_y: f32,
color: [f32; 3],
) -> Self {
self.vertex_data.extend(&[
Vertex {
position: (min_x, min_y).into(),
color,
},
Vertex {
position: (max_x, min_y).into(),
color,
},
Vertex {
position: (max_x, max_y).into(),
color,
},
Vertex {
position: (min_x, max_y).into(),
color,
},
]);
self.index_data.extend(&[
self.current_quad * 4,
self.current_quad * 4 + 1,
self.current_quad * 4 + 2,
self.current_quad * 4,
self.current_quad * 4 + 2,
self.current_quad * 4 + 3,
]);
self.current_quad += 1;
self
}
pub fn build(self, device: &wgpu::Device) -> (StagingBuffer, StagingBuffer, u32) {
(
StagingBuffer::new(device, &self.vertex_data),
StagingBuffer::new(device, &self.index_data),
self.index_data.len() as u32,
)
}
}
pub struct RectBuffers {
pub vertex_buffer: wgpu::Buffer,
pub index_buffer: wgpu::Buffer,
pub num_rects: u32,
}
pub fn create_rect_buffers(
gpu_device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
) -> RectBuffers {
// Test Rectangles
let test_rect_1 = Rect {
top_left_coords: (-0.2, 0.6).into(),
width: 0.1,
height: 0.5,
color: [0.0, 0.0, 1.0],
};
let test_rect_2 = Rect {
top_left_coords: (-0.5, 0.0).into(),
width: 0.5,
height: 0.5,
color: [0.0, 1.0, 0.0],
};
let test_rect_3 = Rect {
top_left_coords: (0.3, 0.3).into(),
width: 0.6,
height: 0.1,
color: [1.0, 0.0, 0.0],
};
let vertex_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: Vertex::SIZE * 4 * 3,
usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST,
mapped_at_creation: false,
});
let u32_size = std::mem::size_of::<u32>() as wgpu::BufferAddress;
let index_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: u32_size * 6 * 3,
usage: wgpu::BufferUsage::INDEX | wgpu::BufferUsage::COPY_DST,
mapped_at_creation: false,
});
let num_rects = {
let (stg_vertex, stg_index, num_indices) = QuadBufferBuilder::new()
.push_rect(&test_rect_1)
.push_rect(&test_rect_2)
.push_rect(&test_rect_3)
.build(&gpu_device);
stg_vertex.copy_to_buffer(encoder, &vertex_buffer);
stg_index.copy_to_buffer(encoder, &index_buffer);
num_indices
};
RectBuffers {
vertex_buffer,
index_buffer,
num_rects,
}
}
pub struct StagingBuffer {
buffer: wgpu::Buffer,
size: wgpu::BufferAddress,
}
impl StagingBuffer {
pub fn new<T: bytemuck::Pod + Sized>(device: &wgpu::Device, data: &[T]) -> StagingBuffer {
StagingBuffer {
buffer: device.create_buffer_init(&BufferInitDescriptor {
contents: bytemuck::cast_slice(data),
usage: wgpu::BufferUsage::COPY_SRC,
label: Some("Staging Buffer"),
}),
size: size_of_slice(data) as wgpu::BufferAddress,
}
}
pub fn copy_to_buffer(&self, encoder: &mut wgpu::CommandEncoder, other: &wgpu::Buffer) {
encoder.copy_buffer_to_buffer(&self.buffer, 0, other, 0, self.size)
}
}

97
editor/src/file.rs Normal file
View file

@ -0,0 +1,97 @@
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_fmt::def::fmt_def;
use roc_fmt::module::fmt_module;
use roc_parse::ast::{Attempting, Def, Module};
use roc_parse::module::module_defs;
use roc_parse::parser;
use roc_parse::parser::Parser;
use roc_region::all::Located;
use std::ffi::OsStr;
use std::path::Path;
use std::{fs, io};
#[derive(Debug)]
pub struct File<'a> {
path: &'a Path,
module_header: Module<'a>,
content: Vec<'a, Located<Def<'a>>>,
}
#[derive(Debug)]
pub enum ReadError {
Read(std::io::Error),
ParseDefs(parser::Fail),
ParseHeader(parser::Fail),
DoesntHaveRocExtension,
}
impl<'a> File<'a> {
pub fn read(path: &'a Path, arena: &'a Bump) -> Result<File<'a>, ReadError> {
if path.extension() != Some(OsStr::new("roc")) {
return Err(ReadError::DoesntHaveRocExtension);
}
let bytes = fs::read(path).map_err(ReadError::Read)?;
let allocation = arena.alloc(bytes);
let module_parse_state = parser::State::new(allocation, Attempting::Module);
let parsed_module = roc_parse::module::header().parse(&arena, module_parse_state);
match parsed_module {
Ok((module, state)) => {
let parsed_defs = module_defs().parse(&arena, state);
match parsed_defs {
Ok((defs, _)) => Ok(File {
path,
module_header: module,
content: defs,
}),
Err((error, _)) => Err(ReadError::ParseDefs(error)),
}
}
Err((error, _)) => Err(ReadError::ParseHeader(error)),
}
}
pub fn fmt(&self) -> String {
let arena = Bump::new();
let mut formatted_file = String::new();
let mut module_header_buf = bumpalo::collections::String::new_in(&arena);
fmt_module(&mut module_header_buf, &self.module_header);
formatted_file.push_str(module_header_buf.as_str());
for def in &self.content {
let mut def_buf = bumpalo::collections::String::new_in(&arena);
fmt_def(&mut def_buf, &def.value, 0);
formatted_file.push_str(def_buf.as_str());
}
formatted_file
}
pub fn fmt_then_write_to(&self, write_path: &'a Path) -> io::Result<()> {
let formatted_file = self.fmt();
fs::write(write_path, formatted_file)
}
pub fn fmt_then_write_with_name(&self, new_name: &str) -> io::Result<()> {
self.fmt_then_write_to(
self.path
.with_file_name(new_name)
.with_extension("roc")
.as_path(),
)
}
pub fn fmt_then_write(&self) -> io::Result<()> {
self.fmt_then_write_to(self.path)
}
}

View file

@ -0,0 +1,153 @@
use winit::event::{ElementState, ModifiersState, VirtualKeyCode};
pub fn handle_keydown(
elem_state: ElementState,
virtual_keycode: VirtualKeyCode,
_modifiers: ModifiersState,
) {
use winit::event::VirtualKeyCode::*;
if let ElementState::Released = elem_state {
return;
}
match virtual_keycode {
Copy => {
todo!("copy");
}
Paste => {
todo!("paste");
}
Cut => {
todo!("cut");
}
_ => {}
}
}
// pub fn handle_text_input(
// text_state: &mut String,
// elem_state: ElementState,
// virtual_keycode: VirtualKeyCode,
// _modifiers: ModifiersState,
// ) {
// use winit::event::VirtualKeyCode::*;
// if let ElementState::Released = elem_state {
// return;
// }
// match virtual_keycode {
// Key1 | Numpad1 => text_state.push('1'),
// Key2 | Numpad2 => text_state.push('2'),
// Key3 | Numpad3 => text_state.push('3'),
// Key4 | Numpad4 => text_state.push('4'),
// Key5 | Numpad5 => text_state.push('5'),
// Key6 | Numpad6 => text_state.push('6'),
// Key7 | Numpad7 => text_state.push('7'),
// Key8 | Numpad8 => text_state.push('8'),
// Key9 | Numpad9 => text_state.push('9'),
// Key0 | Numpad0 => text_state.push('0'),
// A => text_state.push('a'),
// B => text_state.push('b'),
// C => text_state.push('c'),
// D => text_state.push('d'),
// E => text_state.push('e'),
// F => text_state.push('f'),
// G => text_state.push('g'),
// H => text_state.push('h'),
// I => text_state.push('i'),
// J => text_state.push('j'),
// K => text_state.push('k'),
// L => text_state.push('l'),
// M => text_state.push('m'),
// N => text_state.push('n'),
// O => text_state.push('o'),
// P => text_state.push('p'),
// Q => text_state.push('q'),
// R => text_state.push('r'),
// S => text_state.push('s'),
// T => text_state.push('t'),
// U => text_state.push('u'),
// V => text_state.push('v'),
// W => text_state.push('w'),
// X => text_state.push('x'),
// Y => text_state.push('y'),
// Z => text_state.push('z'),
// Escape | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9 | F10 | F11 | F12 | F13 | F14 | F15
// | F16 | F17 | F18 | F19 | F20 | F21 | F22 | F23 | F24 | Snapshot | Scroll | Pause
// | Insert | Home | Delete | End | PageDown | PageUp | Left | Up | Right | Down | Compose
// | Caret | Numlock | AbntC1 | AbntC2 | Ax | Calculator | Capital | Convert | Kana
// | Kanji | LAlt | LBracket | LControl | LShift | LWin | Mail | MediaSelect | PlayPause
// | Power | PrevTrack | MediaStop | Mute | MyComputer | NavigateForward
// | NavigateBackward | NextTrack | NoConvert | OEM102 | RAlt | Sysrq | RBracket
// | RControl | RShift | RWin | Sleep | Stop | Unlabeled | VolumeDown | VolumeUp | Wake
// | WebBack | WebFavorites | WebForward | WebHome | WebRefresh | WebSearch | Apps | Tab
// | WebStop => {
// // TODO handle
// }
// Back => {
// text_state.pop();
// }
// Return | NumpadEnter => {
// text_state.push('\n');
// }
// Space => {
// text_state.push(' ');
// }
// Comma | NumpadComma => {
// text_state.push(',');
// }
// Add => {
// text_state.push('+');
// }
// Apostrophe => {
// text_state.push('\'');
// }
// At => {
// text_state.push('@');
// }
// Backslash => {
// text_state.push('\\');
// }
// Colon => {
// text_state.push(':');
// }
// Period | Decimal => {
// text_state.push('.');
// }
// Equals | NumpadEquals => {
// text_state.push('=');
// }
// Grave => {
// text_state.push('`');
// }
// Minus | Subtract => {
// text_state.push('-');
// }
// Multiply => {
// text_state.push('*');
// }
// Semicolon => {
// text_state.push(';');
// }
// Slash | Divide => {
// text_state.push('/');
// }
// Underline => {
// text_state.push('_');
// }
// Yen => {
// text_state.push('¥');
// }
// Copy => {
// todo!("copy");
// }
// Paste => {
// todo!("paste");
// }
// Cut => {
// todo!("cut");
// }
// }
// }

View file

@ -11,19 +11,29 @@
// re-enable this when working on performance optimizations than have it block PRs.
#![allow(clippy::large_enum_variant)]
use crate::rect::Rect;
// Inspired by https://github.com/sotrh/learn-wgpu
// by Benjamin Hansen, licensed under the MIT license
// See this link to learn wgpu: https://sotrh.github.io/learn-wgpu/
use crate::buffer::create_rect_buffers;
use crate::text::{build_glyph_brush, Text};
use crate::vertex::Vertex;
use std::error::Error;
use std::io;
use std::path::Path;
use wgpu::util::DeviceExt;
use wgpu_glyph::{ab_glyph, GlyphBrushBuilder, Section, Text};
use winit::event::{ElementState, ModifiersState, VirtualKeyCode};
use winit::event;
use winit::event::{Event, ModifiersState};
use winit::event_loop::ControlFlow;
pub mod ast;
pub mod bucket;
mod buffer;
pub mod file;
mod keyboard_input;
mod rect;
pub mod text_state;
pub mod text;
mod util;
mod vertex;
/// The editor is actually launched from the CLI if you pass it zero arguments,
@ -51,7 +61,7 @@ fn run_event_loop() -> Result<(), Box<dyn Error>> {
let surface = unsafe { instance.create_surface(&window) };
// Initialize GPU
let (device, queue) = futures::executor::block_on(async {
let (gpu_device, cmd_queue) = futures::executor::block_on(async {
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
@ -81,59 +91,21 @@ fn run_event_loop() -> Result<(), Box<dyn Error>> {
// Prepare swap chain
let render_format = wgpu::TextureFormat::Bgra8UnormSrgb;
let mut size = window.inner_size();
let mut swap_chain = device.create_swap_chain(
&surface,
&wgpu::SwapChainDescriptor {
let swap_chain_descr = wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: render_format,
width: size.width,
height: size.height,
//Immediate may cause tearing, change present_mode if this becomes a problem
present_mode: wgpu::PresentMode::Immediate,
},
);
};
// Prepare Triangle Pipeline
let triangle_vs_module =
device.create_shader_module(wgpu::include_spirv!("shaders/rect.vert.spv"));
let triangle_fs_module =
device.create_shader_module(wgpu::include_spirv!("shaders/rect.frag.spv"));
let mut swap_chain = gpu_device.create_swap_chain(&surface, &swap_chain_descr);
let triangle_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[],
push_constant_ranges: &[],
});
let rect_pipeline = make_rect_pipeline(&gpu_device, &swap_chain_descr);
let triangle_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(&triangle_pipeline_layout),
vertex_stage: wgpu::ProgrammableStageDescriptor {
module: &triangle_vs_module,
entry_point: "main",
},
fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
module: &triangle_fs_module,
entry_point: "main",
}),
// Use the default rasterizer state: no culling, no depth bias
rasterization_state: None,
primitive_topology: wgpu::PrimitiveTopology::TriangleList,
color_states: &[wgpu::TextureFormat::Bgra8UnormSrgb.into()],
depth_stencil_state: None,
vertex_state: wgpu::VertexStateDescriptor {
index_format: wgpu::IndexFormat::Uint16,
vertex_buffers: &[Vertex::buffer_descriptor()],
},
sample_count: 1,
sample_mask: !0,
alpha_to_coverage_enabled: false,
});
// Prepare glyph_brush
let inconsolata =
ab_glyph::FontArc::try_from_slice(include_bytes!("../Inconsolata-Regular.ttf"))?;
let mut glyph_brush = GlyphBrushBuilder::using_font(inconsolata).build(&device, render_format);
let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?;
let is_animating = true;
let mut text_state = String::new();
@ -152,178 +124,97 @@ fn run_event_loop() -> Result<(), Box<dyn Error>> {
}
match event {
winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::CloseRequested,
Event::WindowEvent {
event: event::WindowEvent::CloseRequested,
..
} => *control_flow = winit::event_loop::ControlFlow::Exit,
winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::Resized(new_size),
Event::WindowEvent {
event: event::WindowEvent::Resized(new_size),
..
} => {
size = new_size;
swap_chain = device.create_swap_chain(
swap_chain = gpu_device.create_swap_chain(
&surface,
&wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: render_format,
width: size.width,
height: size.height,
//Immediate may cause tearing, change present_mode if this becomes a problem
present_mode: wgpu::PresentMode::Immediate,
},
);
}
winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::ReceivedCharacter(ch),
Event::WindowEvent {
event: event::WindowEvent::ReceivedCharacter(ch),
..
} => {
match ch {
'\u{8}' | '\u{7f}' => {
// In Linux, we get a '\u{8}' when you press backspace,
// but in macOS we get '\u{7f}'.
text_state.pop();
update_text_state(&mut text_state, &ch);
}
'\u{e000}'..='\u{f8ff}'
| '\u{f0000}'..='\u{ffffd}'
| '\u{100000}'..='\u{10fffd}' => {
// These are private use characters; ignore them.
// See http://www.unicode.org/faq/private_use.html
}
_ => {
text_state.push(ch);
}
}
}
winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::KeyboardInput { input, .. },
Event::WindowEvent {
event: event::WindowEvent::KeyboardInput { input, .. },
..
} => {
if let Some(virtual_keycode) = input.virtual_keycode {
handle_keydown(input.state, virtual_keycode, keyboard_modifiers);
keyboard_input::handle_keydown(
input.state,
virtual_keycode,
keyboard_modifiers,
);
}
}
winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::ModifiersChanged(modifiers),
Event::WindowEvent {
event: event::WindowEvent::ModifiersChanged(modifiers),
..
} => {
keyboard_modifiers = modifiers;
}
winit::event::Event::MainEventsCleared => window.request_redraw(),
winit::event::Event::RedrawRequested { .. } => {
Event::MainEventsCleared => window.request_redraw(),
Event::RedrawRequested { .. } => {
// Get a command encoder for the current frame
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
let mut encoder =
gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Redraw"),
});
// Get the next frame
let rect_buffers = create_rect_buffers(&gpu_device, &mut encoder);
let frame = swap_chain
.get_current_frame()
.expect("Failed to acquire next swap chain texture")
.expect("Failed to acquire next SwapChainFrame")
.output;
// Test Rectangle
let test_rect_1 = Rect {
top: 0.9,
left: -0.8,
width: 0.2,
height: 0.3,
color: [0.0, 1.0, 1.0],
};
let test_rect_2 = Rect {
top: 0.0,
left: 0.0,
width: 0.5,
height: 0.5,
color: [1.0, 1.0, 0.0],
};
let mut rectangles = Vec::new();
rectangles.extend_from_slice(&test_rect_1.as_array());
rectangles.extend_from_slice(&test_rect_2.as_array());
let mut rect_index_buffers = Vec::new();
rect_index_buffers.extend_from_slice(&Rect::INDEX_BUFFER);
rect_index_buffers.extend_from_slice(&Rect::INDEX_BUFFER);
// Vertex Buffer for drawing rectangles
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(&rectangles),
usage: wgpu::BufferUsage::VERTEX,
});
// Index Buffer for drawing rectangles
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Index Buffer"),
contents: bytemuck::cast_slice(&rect_index_buffers),
usage: wgpu::BufferUsage::INDEX,
});
// Clear frame
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
attachment: &frame.view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.007,
g: 0.007,
b: 0.007,
a: 1.0,
}),
store: true,
},
ops: wgpu::Operations::default(),
}],
depth_stencil_attachment: None,
});
render_pass.set_pipeline(&triangle_pipeline);
render_pass.set_vertex_buffer(
0, // The buffer slot to use for this vertex buffer.
vertex_buffer.slice(..), // Use the entire buffer.
);
render_pass.set_index_buffer(index_buffer.slice(..));
render_pass.draw_indexed(
0..((&rect_index_buffers).len() as u32), // Draw all of the vertices from our test data.
0, // Base Vertex
0..1, // Instances
);
if rect_buffers.num_rects > 0 {
render_pass.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..));
render_pass.set_index_buffer(rect_buffers.index_buffer.slice(..));
render_pass.set_pipeline(&rect_pipeline);
render_pass.draw_indexed(0..rect_buffers.num_rects, 0, 0..1);
}
glyph_brush.queue(Section {
screen_position: (30.0, 30.0),
bounds: (size.width as f32, size.height as f32),
text: vec![Text::new("Enter some text:")
.with_color([0.4666, 0.2, 1.0, 1.0])
.with_scale(40.0)],
..Section::default()
});
drop(render_pass);
glyph_brush.queue(Section {
screen_position: (30.0, 90.0),
bounds: (size.width as f32, size.height as f32),
text: vec![Text::new(format!("{}|", text_state).as_str())
.with_color([1.0, 1.0, 1.0, 1.0])
.with_scale(40.0)],
..Section::default()
});
// Draw the text!
glyph_brush
.draw_queued(
&device,
draw_all_text(
&gpu_device,
&mut staging_belt,
&mut encoder,
&frame.view,
size.width,
size.height,
)
.expect("Draw queued");
&frame,
&size,
&text_state,
&mut glyph_brush,
);
staging_belt.finish();
queue.submit(Some(encoder.finish()));
cmd_queue.submit(Some(encoder.finish()));
// Recall unused staging buffers
use futures::task::SpawnExt;
@ -341,27 +232,126 @@ fn run_event_loop() -> Result<(), Box<dyn Error>> {
})
}
fn handle_keydown(
elem_state: ElementState,
virtual_keycode: VirtualKeyCode,
_modifiers: ModifiersState,
fn make_rect_pipeline(
gpu_device: &wgpu::Device,
swap_chain_descr: &wgpu::SwapChainDescriptor,
) -> wgpu::RenderPipeline {
let pipeline_layout = gpu_device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
bind_group_layouts: &[],
push_constant_ranges: &[],
label: Some("Pipeline Layout"),
});
let pipeline = create_render_pipeline(
&gpu_device,
&pipeline_layout,
swap_chain_descr.format,
&[Vertex::DESC],
wgpu::include_spirv!("shaders/rect.vert.spv"),
wgpu::include_spirv!("shaders/rect.frag.spv"),
);
pipeline
}
fn create_render_pipeline(
device: &wgpu::Device,
layout: &wgpu::PipelineLayout,
color_format: wgpu::TextureFormat,
vertex_descs: &[wgpu::VertexBufferDescriptor],
vs_src: wgpu::ShaderModuleSource,
fs_src: wgpu::ShaderModuleSource,
) -> wgpu::RenderPipeline {
let vs_module = device.create_shader_module(vs_src);
let fs_module = device.create_shader_module(fs_src);
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Render Pipeline"),
layout: Some(&layout),
vertex_stage: wgpu::ProgrammableStageDescriptor {
module: &vs_module,
entry_point: "main",
},
fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
module: &fs_module,
entry_point: "main",
}),
rasterization_state: None,
primitive_topology: wgpu::PrimitiveTopology::TriangleList,
color_states: &[wgpu::ColorStateDescriptor {
format: color_format,
color_blend: wgpu::BlendDescriptor::REPLACE,
alpha_blend: wgpu::BlendDescriptor::REPLACE,
write_mask: wgpu::ColorWrite::ALL,
}],
depth_stencil_state: None,
sample_count: 1,
sample_mask: !0,
alpha_to_coverage_enabled: false,
vertex_state: wgpu::VertexStateDescriptor {
index_format: wgpu::IndexFormat::Uint32,
vertex_buffers: vertex_descs,
},
})
}
fn draw_all_text(
gpu_device: &wgpu::Device,
staging_belt: &mut wgpu::util::StagingBelt,
encoder: &mut wgpu::CommandEncoder,
frame: &wgpu::SwapChainTexture,
size: &winit::dpi::PhysicalSize<u32>,
text_state: &str,
glyph_brush: &mut wgpu_glyph::GlyphBrush<()>,
) {
use winit::event::VirtualKeyCode::*;
let bounds = (size.width as f32, size.height as f32).into();
if let ElementState::Released = elem_state {
return;
let main_label = Text {
position: (30.0, 30.0).into(),
bounds,
color: (0.4666, 0.2, 1.0, 1.0).into(),
text: String::from("Enter some text:"),
size: 40.0,
..Default::default()
};
let code_text = Text {
position: (30.0, 90.0).into(),
bounds,
color: (1.0, 1.0, 1.0, 1.0).into(),
text: String::from(format!("{}|", text_state).as_str()),
size: 40.0,
..Default::default()
};
text::queue_text_draw(&main_label, glyph_brush);
text::queue_text_draw(&code_text, glyph_brush);
glyph_brush
.draw_queued(
gpu_device,
staging_belt,
encoder,
&frame.view,
size.width,
size.height,
)
.expect("Draw queued");
}
match virtual_keycode {
Copy => {
todo!("copy");
fn update_text_state(text_state: &mut String, received_char: &char) {
match received_char {
'\u{8}' | '\u{7f}' => {
// In Linux, we get a '\u{8}' when you press backspace,
// but in macOS we get '\u{7f}'.
text_state.pop();
}
Paste => {
todo!("paste");
'\u{e000}'..='\u{f8ff}' | '\u{f0000}'..='\u{ffffd}' | '\u{100000}'..='\u{10fffd}' => {
// These are private use characters; ignore them.
// See http://www.unicode.org/faq/private_use.html
}
_ => {
text_state.push(*received_char);
}
Cut => {
todo!("cut");
}
_ => {}
}
}

View file

@ -1,35 +1,8 @@
use crate::vertex::Vertex;
use cgmath::Vector2;
pub struct Rect {
pub top: f32,
pub left: f32,
pub top_left_coords: Vector2<f32>,
pub width: f32,
pub height: f32,
pub color: [f32; 3],
}
impl Rect {
pub fn as_array(&self) -> [Vertex; 4] {
[
Vertex {
position: [self.left, self.top, 0.0],
color: self.color,
},
Vertex {
position: [self.left + self.width, self.top, 0.0],
color: self.color,
},
Vertex {
position: [self.left + self.width, self.top - self.height, 0.0],
color: self.color,
},
Vertex {
position: [self.left, self.top - self.height, 0.0],
color: self.color,
},
]
}
// Currently broken - needs to be offset when additional rectangles are appended
pub const INDEX_BUFFER: [u16; 6] = [0, 1, 3, 1, 2, 3];
}

View file

@ -1,11 +1,11 @@
#version 450
// Taken from https://github.com/sotrh/learn-wgpu
// by Benjamin Hansen, licensed under the MIT license
// The fragment shader's "in" values come from the "out" values of the vertex shader.
layout(location=0) in vec3 color;
// The actual color that is rendered to the screen based on the vertex.
layout(location=0) out vec4 f_color;
layout(location=0) out vec4 fColor;
void main() {
f_color = vec4(color, 1.0);
fColor = vec4(color, 1.0);
}

View file

@ -1,14 +1,13 @@
#version 450
// Taken from https://github.com/sotrh/learn-wgpu
// by Benjamin Hansen, licensed under the MIT license
// Layout value labelled "in" acquire data from the vertex buffer,
// as defined in the buffer descriptor for this shader.
layout(location=0) in vec3 position;
layout(location=1) in vec3 color;
layout(location=0) in vec2 aPosition;
layout(location=1) in vec3 aColor;
// Layout values labelled "out" send their data to the fragment shader.
layout(location=0) out vec3 v_color;
layout(location=0) out vec3 vColor;
void main() {
v_color = color;
gl_Position = vec4(position, 1.0);
gl_Position = vec4(aPosition, 0, 1);
vColor = aColor;
}

62
editor/src/text.rs Normal file
View file

@ -0,0 +1,62 @@
// Adapted from https://github.com/sotrh/learn-wgpu
// by Benjamin Hansen, licensed under the MIT license
use ab_glyph::{FontArc, InvalidFont};
use cgmath::{Vector2, Vector4};
use wgpu_glyph::{ab_glyph, GlyphBrush, GlyphBrushBuilder, Section};
#[derive(Debug)]
pub struct Text {
pub position: Vector2<f32>,
pub bounds: Vector2<f32>,
pub color: Vector4<f32>,
pub text: String,
pub size: f32,
pub visible: bool,
pub centered: bool,
}
impl Default for Text {
fn default() -> Self {
Self {
position: (0.0, 0.0).into(),
bounds: (std::f32::INFINITY, std::f32::INFINITY).into(),
color: (1.0, 1.0, 1.0, 1.0).into(),
text: String::new(),
size: 16.0,
visible: true,
centered: false,
}
}
}
pub fn queue_text_draw(text: &Text, glyph_brush: &mut GlyphBrush<()>) {
let layout = wgpu_glyph::Layout::default().h_align(if text.centered {
wgpu_glyph::HorizontalAlign::Center
} else {
wgpu_glyph::HorizontalAlign::Left
});
let section = Section {
screen_position: text.position.into(),
bounds: text.bounds.into(),
layout,
..Section::default()
}
.add_text(
wgpu_glyph::Text::new(&text.text)
.with_color(text.color)
.with_scale(text.size),
);
glyph_brush.queue(section);
}
pub fn build_glyph_brush(
gpu_device: &wgpu::Device,
render_format: wgpu::TextureFormat,
) -> Result<GlyphBrush<()>, InvalidFont> {
let inconsolata = FontArc::try_from_slice(include_bytes!("../Inconsolata-Regular.ttf"))?;
Ok(GlyphBrushBuilder::using_font(inconsolata).build(&gpu_device, render_format))
}

View file

@ -1,128 +0,0 @@
use winit::event::{ElementState, ModifiersState, VirtualKeyCode};
pub fn handle_text_input(
text_state: &mut String,
elem_state: ElementState,
virtual_keycode: VirtualKeyCode,
_modifiers: ModifiersState,
) {
use winit::event::VirtualKeyCode::*;
if let ElementState::Released = elem_state {
return;
}
match virtual_keycode {
Key1 | Numpad1 => text_state.push('1'),
Key2 | Numpad2 => text_state.push('2'),
Key3 | Numpad3 => text_state.push('3'),
Key4 | Numpad4 => text_state.push('4'),
Key5 | Numpad5 => text_state.push('5'),
Key6 | Numpad6 => text_state.push('6'),
Key7 | Numpad7 => text_state.push('7'),
Key8 | Numpad8 => text_state.push('8'),
Key9 | Numpad9 => text_state.push('9'),
Key0 | Numpad0 => text_state.push('0'),
A => text_state.push('a'),
B => text_state.push('b'),
C => text_state.push('c'),
D => text_state.push('d'),
E => text_state.push('e'),
F => text_state.push('f'),
G => text_state.push('g'),
H => text_state.push('h'),
I => text_state.push('i'),
J => text_state.push('j'),
K => text_state.push('k'),
L => text_state.push('l'),
M => text_state.push('m'),
N => text_state.push('n'),
O => text_state.push('o'),
P => text_state.push('p'),
Q => text_state.push('q'),
R => text_state.push('r'),
S => text_state.push('s'),
T => text_state.push('t'),
U => text_state.push('u'),
V => text_state.push('v'),
W => text_state.push('w'),
X => text_state.push('x'),
Y => text_state.push('y'),
Z => text_state.push('z'),
Escape | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9 | F10 | F11 | F12 | F13 | F14 | F15
| F16 | F17 | F18 | F19 | F20 | F21 | F22 | F23 | F24 | Snapshot | Scroll | Pause
| Insert | Home | Delete | End | PageDown | PageUp | Left | Up | Right | Down | Compose
| Caret | Numlock | AbntC1 | AbntC2 | Ax | Calculator | Capital | Convert | Kana
| Kanji | LAlt | LBracket | LControl | LShift | LWin | Mail | MediaSelect | PlayPause
| Power | PrevTrack | MediaStop | Mute | MyComputer | NavigateForward
| NavigateBackward | NextTrack | NoConvert | OEM102 | RAlt | Sysrq | RBracket
| RControl | RShift | RWin | Sleep | Stop | Unlabeled | VolumeDown | VolumeUp | Wake
| WebBack | WebFavorites | WebForward | WebHome | WebRefresh | WebSearch | Apps | Tab
| WebStop => {
// TODO handle
}
Back => {
text_state.pop();
}
Return | NumpadEnter => {
text_state.push('\n');
}
Space => {
text_state.push(' ');
}
Comma | NumpadComma => {
text_state.push(',');
}
Add => {
text_state.push('+');
}
Apostrophe => {
text_state.push('\'');
}
At => {
text_state.push('@');
}
Backslash => {
text_state.push('\\');
}
Colon => {
text_state.push(':');
}
Period | Decimal => {
text_state.push('.');
}
Equals | NumpadEquals => {
text_state.push('=');
}
Grave => {
text_state.push('`');
}
Minus | Subtract => {
text_state.push('-');
}
Multiply => {
text_state.push('*');
}
Semicolon => {
text_state.push(';');
}
Slash | Divide => {
text_state.push('/');
}
Underline => {
text_state.push('_');
}
Yen => {
text_state.push('¥');
}
Copy => {
todo!("copy");
}
Paste => {
todo!("paste");
}
Cut => {
todo!("cut");
}
}
}

5
editor/src/util.rs Normal file
View file

@ -0,0 +1,5 @@
// Taken from https://github.com/sotrh/learn-wgpu
// by Benjamin Hansen, licensed under the MIT license
pub fn size_of_slice<T: Sized>(slice: &[T]) -> usize {
std::mem::size_of::<T>() * slice.len()
}

View file

@ -1,7 +1,11 @@
#[repr(C)]
#[derive(Copy, Clone, Debug)]
// Taken from https://github.com/sotrh/learn-wgpu
// by Benjamin Hansen, licensed under the MIT license
use cgmath::Vector2;
#[derive(Copy, Clone)]
pub struct Vertex {
pub position: [f32; 3],
#[allow(dead_code)]
pub position: Vector2<f32>,
pub color: [f32; 3],
}
@ -9,25 +13,23 @@ unsafe impl bytemuck::Pod for Vertex {}
unsafe impl bytemuck::Zeroable for Vertex {}
impl Vertex {
// Defines how the shader will use this data structure.
pub fn buffer_descriptor<'a>() -> wgpu::VertexBufferDescriptor<'a> {
wgpu::VertexBufferDescriptor {
stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
pub const SIZE: wgpu::BufferAddress = std::mem::size_of::<Self>() as wgpu::BufferAddress;
pub const DESC: wgpu::VertexBufferDescriptor<'static> = wgpu::VertexBufferDescriptor {
stride: Self::SIZE,
step_mode: wgpu::InputStepMode::Vertex,
attributes: &[
// position
wgpu::VertexAttributeDescriptor {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float3,
format: wgpu::VertexFormat::Float2,
},
// color
wgpu::VertexAttributeDescriptor {
offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
shader_location: 1,
format: wgpu::VertexFormat::Float3,
},
],
}
}
};
}

View file

@ -0,0 +1,26 @@
interface Simple
exposes [
v, x
]
imports []
v : Str
v = "Value!"
x : Int
x = 4

View file

@ -0,0 +1,68 @@
interface Storage
exposes [
Storage,
decoder,
get,
listener,
set
]
imports [
Map.{ Map },
Json.Decode.{ Decoder } as Decode
Json.Encode as Encode
Ports.FromJs as FromJs
Ports.ToJs as ToJs
]
################################################################################
## TYPES ##
################################################################################
Storage : [
@Storage (Map Str Decode.Value)
]
################################################################################
## API ##
################################################################################
get : Storage, Str, Decoder a -> [ Ok a, NotInStorage, DecodeError Decode.Error ]*
get = \key, decoder, @Storage map ->
when Map.get map key is
Ok json ->
Decode.decodeValue decoder json
Err NotFound ->
NotInStorage
set : Encode.Value, Str -> Effect {}
set json str =
ToJs.type "setStorage"
|> ToJs.setFields [
Field "key" (Encode.str str),
Field "value" json
]
|> ToJs.send
decoder : Decoder Storage
decoder =
Decode.mapType Decode.value
|> Decode.map \map -> @Storage map
################################################################################
## PORTS INCOMING ##
################################################################################
listener : (Storage -> msg) -> FromJs.Listener msg
listener toMsg =
FromJs.listen "storageUpdated"
(Decode.map decoder toMsg)

40
editor/tests/test_file.rs Normal file
View file

@ -0,0 +1,40 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
#[cfg(test)]
mod test_file {
use bumpalo::Bump;
use roc_editor::file::File;
use std::path::Path;
#[test]
fn read_and_fmt_simple_roc_module() {
let simple_module_path = Path::new("./tests/modules/Simple.roc");
let arena = Bump::new();
let file = File::read(simple_module_path, &arena)
.expect("Could not read Simple.roc in test_file test");
assert_eq!(
file.fmt(),
indoc!(
r#"
interface Simple
exposes [
v, x
]
imports []
v : Str
v = "Value!"
x : Int
x = 4"#
)
);
}
}

1
examples/balance/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
main

View file

@ -1,7 +1,7 @@
app Main provides [ rocMain ] imports [ Effect ]
app "main" imports [ Effect ] provides [ rocMain ] to "./platform"
rocMain : Effect.Effect {} as Fx
rocMain =
when List.len (Str.split "hello" "JJJJ there") is
_ -> Effect.putLine "Yay"

View file

@ -1,7 +1,9 @@
platform folkertdev/foo
provides [ mainForHost ]
requires { main : Effect {} }
requires { rocMain : Effect {} }
exposes []
packages {}
imports []
provides [ mainForHost ]
effects Effect
{
putChar : Int -> Effect {},
@ -10,4 +12,4 @@ platform folkertdev/foo
}
mainForHost : Effect {} as Fx
mainForHost = main
mainForHost = rocMain

View file

@ -7,16 +7,16 @@ use std::alloc::Layout;
use std::time::SystemTime;
extern "C" {
#[link_name = "Main_rocMain_1_exposed"]
#[link_name = "roc__rocMain_1_exposed"]
fn roc_main(output: *mut u8) -> ();
#[link_name = "Main_rocMain_1_size"]
#[link_name = "roc__rocMain_1_size"]
fn roc_main_size() -> i64;
#[link_name = "Main_rocMain_1_Fx_caller"]
#[link_name = "roc__rocMain_1_Fx_caller"]
fn call_Fx(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> ();
#[link_name = "Main_rocMain_1_Fx_size"]
#[link_name = "roc__rocMain_1_Fx_size"]
fn size_Fx() -> i64;
}
@ -62,11 +62,10 @@ unsafe fn call_the_closure(function_pointer: *const u8, closure_data_ptr: *const
let output = &*(buffer as *mut RocCallResult<i64>);
// match output.into() {
// Ok(v) => v,
// Err(e) => panic!("failed with {}", e),
// }
32
match output.into() {
Ok(_) => 0,
Err(e) => panic!("failed with {}", e),
}
})
}
@ -96,7 +95,12 @@ pub fn rust_main() -> isize {
let closure_data_ptr = buffer.offset(16);
call_the_closure(function_pointer as *const u8, closure_data_ptr as *const u8)
let result =
call_the_closure(function_pointer as *const u8, closure_data_ptr as *const u8);
std::alloc::dealloc(buffer, layout);
result
}
Err(msg) => {
std::alloc::dealloc(buffer, layout);

1
examples/closure/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
closure

View file

@ -1,6 +1,6 @@
app "closure" provides [ makeClosure ] to "./platform/"
makeClosure : ({} -> Int) as MyClosure
makeClosure : ({} -> I64) as MyClosure
makeClosure =
x = 42
y = 42

View file

@ -1,19 +1,21 @@
#![allow(non_snake_case)]
use roc_std::alloca;
use roc_std::RocCallResult;
use std::alloc::Layout;
use std::time::SystemTime;
extern "C" {
#[link_name = "Main_makeClosure_1_exposed"]
#[link_name = "roc__makeClosure_1_exposed"]
fn make_closure(output: *mut u8) -> ();
#[link_name = "Main_makeClosure_1_size"]
#[link_name = "roc__makeClosure_1_size"]
fn closure_size() -> i64;
#[link_name = "Main_makeClosure_1_MyClosure_caller"]
#[link_name = "roc__makeClosure_1_MyClosure_caller"]
fn call_MyClosure(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> ();
#[link_name = "Main_makeClosure_1_MyClosure_size"]
#[link_name = "roc__makeClosure_1_MyClosure_size"]
fn size_MyClosure() -> i64;
}
@ -65,7 +67,12 @@ pub fn rust_main() -> isize {
let closure_data_ptr = buffer.offset(16);
call_the_closure(function_pointer as *const u8, closure_data_ptr as *const u8)
let result =
call_the_closure(function_pointer as *const u8, closure_data_ptr as *const u8);
std::alloc::dealloc(buffer, layout);
result
}
Err(msg) => {
std::alloc::dealloc(buffer, layout);
@ -80,7 +87,6 @@ pub fn rust_main() -> isize {
println!(
"Roc closure took {:.4} ms to compute this answer: {:?}",
duration.as_secs_f64() * 1000.0,
// truncate the answer, so stdout is not swamped
answer
);

1
examples/effect/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
effect-example

View file

@ -5,7 +5,7 @@ ConsList a : [ Cons a (ConsList a), Nil ]
empty : ConsList a
empty = Nil
len : ConsList a -> Int
len : ConsList a -> I64
len = \list ->
when list is
Cons _ rest -> 1 + len rest

View file

@ -1,11 +1,16 @@
app "effect-example" imports [ Effect ] provides [ main ] to "./platform"
app "effect-example"
packages { base: "thing/platform-dir" }
imports [ base.Task.{ Task, after } ]
provides [ main ] to base
main : Effect.Effect {} as Fx
# TODO `main : Task {}` does not work
# it will then think that the `Task` module is unused
# (if we also don't use any of the other importd symbols)
main : Task.Task {} as Fx
main =
when if 1 == 1 then True 3 else False 3.14 is
True n -> Effect.putLine (Str.fromInt n)
_ -> Effect.putLine "Yay"
True n -> Task.putLine (Str.fromInt n)
_ -> Task.putLine "Yay"
# main : Effect.Effect {} as Fx
# main =

View file

@ -21,11 +21,11 @@ singleton = \key, value ->
Node Black key value Empty Empty
# {-| Determine the number of key-value pairs in the dictionary. -}
size : Dict k v -> Int
size : Dict k v -> I64
size = \dict ->
sizeHelp 0 dict
sizeHelp : Int, Dict k v -> Int
sizeHelp : I64, Dict k v -> I64
sizeHelp = \n, dict ->
when dict is
Empty ->

View file

@ -8,6 +8,6 @@ edition = "2018"
crate-type = ["staticlib"]
[dependencies]
roc_std = { path = "../../../roc_std" }
roc_std = { path = "../../../../roc_std" }
[workspace]

View file

@ -1,6 +1,6 @@
platform folkertdev/foo
requires { main : Effect {} }
exposes []
exposes [ Task ]
packages {}
imports []
provides [ mainForHost ]

View file

@ -0,0 +1,9 @@
interface Task
exposes [ Task, putLine, after ]
imports [ Effect ]
Task a : Effect.Effect a
putLine = \line -> Effect.putLine line
after = Effect.after

View file

@ -1,9 +1,9 @@
app "quicksort" imports [ Utils.{ swap } ] provides [ quicksort ] to "./platform"
quicksort : List Int -> List Int
quicksort : List I64 -> List I64
quicksort = \originalList ->
quicksortHelp : List (Num a), Int, Int -> List (Num a)
quicksortHelp : List (Num a), I64, I64 -> List (Num a)
quicksortHelp = \list, low, high ->
if low < high then
when partition low high list is
@ -14,7 +14,7 @@ quicksort = \originalList ->
else
list
partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -26,7 +26,7 @@ quicksort = \originalList ->
Pair (low - 1) initialList
partitionHelp : Int, Int, List (Num a), Int, (Num a) -> [ Pair Int (List (Num a)) ]
partitionHelp : I64, I64, List (Num a), I64, (Num a) -> [ Pair I64 (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is

View file

@ -1,6 +1,6 @@
interface Utils exposes [ swap ] imports []
swap : Int, Int, List a -> List a
swap : I64, I64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->

View file

@ -2,7 +2,7 @@ app "quicksort" provides [ quicksort ] to "./platform"
quicksort = \originalList ->
quicksortHelp : List (Num a), Int, Int -> List (Num a)
quicksortHelp : List (Num a), I64, I64 -> List (Num a)
quicksortHelp = \list, low, high ->
if low < high then
when partition low high list is
@ -14,7 +14,7 @@ quicksort = \originalList ->
list
partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -25,7 +25,7 @@ quicksort = \originalList ->
Err _ ->
Pair (low - 1) initialList
partitionHelp : Int, Int, List (Num a), Int, (Num a) -> [ Pair Int (List (Num a)) ]
partitionHelp : I64, I64, List (Num a), I64, (Num a) -> [ Pair I64 (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is
@ -41,7 +41,7 @@ quicksort = \originalList ->
Pair i list
swap : Int, Int, List a -> List a
swap : I64, I64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->

1
examples/shared-quicksort/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
quicksort

View file

@ -1,12 +1,12 @@
app "quicksort" packages { base: "./platform" } provides [ quicksort ] to base
quicksort : List Int -> List Int
quicksort : List I64 -> List I64
quicksort = \originalList -> helper originalList
helper : List Int -> List Int
helper : List I64 -> List I64
helper = \originalList ->
quicksortHelp : List (Num a), Int, Int -> List (Num a)
quicksortHelp : List (Num a), I64, I64 -> List (Num a)
quicksortHelp = \list, low, high ->
if low < high then
when partition low high list is
@ -18,7 +18,7 @@ helper = \originalList ->
list
swap : Int, Int, List a -> List a
swap : I64, I64, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
@ -29,7 +29,7 @@ helper = \originalList ->
_ ->
[]
partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
@ -41,7 +41,7 @@ helper = \originalList ->
Pair (low - 1) initialList
partitionHelp : Int, Int, List (Num a), Int, (Num a) -> [ Pair Int (List (Num a)) ]
partitionHelp : I64, I64, List (Num a), I64, (Num a) -> [ Pair I64 (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is

View file

@ -1,9 +1,11 @@
#![allow(non_snake_case)]
use roc_std::RocCallResult;
use roc_std::RocList;
use std::time::SystemTime;
extern "C" {
#[link_name = "_quicksort_1_exposed"]
#[link_name = "roc__quicksort_1_exposed"]
fn quicksort(list: RocList<i64>, output: &mut RocCallResult<RocList<i64>>) -> ();
}

View file

@ -39,6 +39,7 @@ let
linux-inputs =
if isLinux then
[
valgrind
vulkan-headers
vulkan-loader
vulkan-tools
@ -71,7 +72,6 @@ let
python3
llvmPkgs.llvm
llvmPkgs.clang
valgrind
pkg-config
zig
# llb deps