Merge branch 'trunk' into wasm_arrays

This commit is contained in:
rvcas 2021-12-16 21:07:12 -05:00
commit c14b4b81e0
82 changed files with 1947 additions and 1315 deletions

View file

@ -60,3 +60,4 @@ Yann Simon <yann.simon.fr@gmail.com>
Shahn Hogan <shahnhogan@hotmail.com>
Tankor Smash <tankorsmash+github@gmail.com>
Matthias Devlamynck <matthias.devlamynck@mailoo.org>
Jan Van Bruggen <JanCVanB@users.noreply.github.com>

View file

@ -22,6 +22,7 @@ Earthly may temporarily use a lot of disk space, up to 90 GB. This disk space is
- Before making your first pull request, definitely talk to an existing contributor on [Roc Zulip](https://roc.zulipchat.com) first about what you plan to do! This can not only avoid duplicated effort, it can also avoid making a whole PR only to discover it won't be accepted because the change doesn't fit with the goals of the language's design or implementation.
- It's a good idea to open a work-in-progress pull request as you begin working on something. This way, others can see that you're working on it, which avoids duplicate effort, and others can give feedback sooner rather than later if they notice a problem in the direction things are going. Be sure to include "WIP" in the title of the PR as long as it's not ready for review!
- Make sure to create a branch on the roc repository for your changes. We do not allow CI to be run on forks for security.
- You find good first issues [here](https://github.com/rtfeldman/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
## Can we do better?

1
Cargo.lock generated
View file

@ -3411,6 +3411,7 @@ dependencies = [
"roc_collections",
"roc_module",
"roc_mono",
"roc_reporting",
"roc_std",
"target-lexicon",
]

View file

@ -1053,7 +1053,7 @@ Let's take a closer look at the part of `Hello.roc` above `main`:
```coffee
app "hello"
packages [ pf: "examples/cli/platform" ]
packages { pf: "examples/cli/platform" }
imports [ pf.Stdout ]
provides main to pf
```
@ -1071,12 +1071,12 @@ without running it by running `roc build Hello.roc`.
The remaining lines all involve the *platform* this application is built on:
```coffee
packages [ pf: "examples/cli/platform" ]
packages { pf: "examples/cli/platform" }
imports [ pf.Stdout ]
provides main to pf
```
The `packages [ pf: "examples/cli/platform" ]` part says two things:
The `packages { pf: "examples/cli/platform" }` part says two things:
- We're going to be using a *package* (that is, a collection of modules) called `"examples/cli/platform"`
- We're going to name that package `pf` so we can refer to it more concisely in the future.
@ -1099,7 +1099,7 @@ When we write `imports [ pf.Stdout ]`, it specifies that the `Stdout`
module comes from the `pf` package.
Since `pf` was the name we chose for the `examples/cli/platform` package
(when we wrote `packages [ pf: "examples/cli/platform" ]`), this `imports` line
(when we wrote `packages { pf: "examples/cli/platform" }`), this `imports` line
tells the Roc compiler that when we call `Stdout.line`, it should look for that
`line` function in the `Stdout` module of the `examples/cli/platform` package.

View file

@ -7,11 +7,11 @@ use roc_fmt::module::fmt_module;
use roc_fmt::Buf;
use roc_module::called_via::{BinOp, UnaryOp};
use roc_parse::ast::{
AssignedField, Collection, Expr, Pattern, StrLiteral, StrSegment, Tag, TypeAnnotation,
AssignedField, Collection, Expr, Pattern, Spaced, StrLiteral, StrSegment, Tag, TypeAnnotation,
WhenBranch,
};
use roc_parse::header::{
AppHeader, Effects, ExposesEntry, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageName, PackageOrPath, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent,
};
use roc_parse::{
@ -228,16 +228,22 @@ impl<'a> RemoveSpaces<'a> for &'a str {
}
}
impl<'a, T: RemoveSpaces<'a> + Copy> RemoveSpaces<'a> for ExposesEntry<'a, T> {
impl<'a, T: RemoveSpaces<'a> + Copy> RemoveSpaces<'a> for Spaced<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
ExposesEntry::Exposed(a) => ExposesEntry::Exposed(a.remove_spaces(arena)),
ExposesEntry::SpaceBefore(a, _) => a.remove_spaces(arena),
ExposesEntry::SpaceAfter(a, _) => a.remove_spaces(arena),
Spaced::Item(a) => Spaced::Item(a.remove_spaces(arena)),
Spaced::SpaceBefore(a, _) => a.remove_spaces(arena),
Spaced::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ExposedName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for ModuleName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
@ -261,18 +267,10 @@ impl<'a> RemoveSpaces<'a> for To<'a> {
impl<'a> RemoveSpaces<'a> for TypedIdent<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
TypedIdent::Entry {
ident,
spaces_before_colon: _,
ann,
} => TypedIdent::Entry {
ident: ident.remove_spaces(arena),
TypedIdent {
ident: self.ident.remove_spaces(arena),
spaces_before_colon: &[],
ann: ann.remove_spaces(arena),
},
TypedIdent::SpaceBefore(a, _) => a.remove_spaces(arena),
TypedIdent::SpaceAfter(a, _) => a.remove_spaces(arena),
ann: self.ann.remove_spaces(arena),
}
}
}
@ -287,29 +285,17 @@ impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> {
}
impl<'a> RemoveSpaces<'a> for PlatformRigid<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
PlatformRigid::Entry { rigid, alias } => PlatformRigid::Entry { rigid, alias },
PlatformRigid::SpaceBefore(a, _) => a.remove_spaces(arena),
PlatformRigid::SpaceAfter(a, _) => a.remove_spaces(arena),
}
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for PackageEntry<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
PackageEntry::Entry {
shorthand,
spaces_after_shorthand: _,
package_or_path,
} => PackageEntry::Entry {
shorthand,
PackageEntry {
shorthand: self.shorthand,
spaces_after_shorthand: &[],
package_or_path: package_or_path.remove_spaces(arena),
},
PackageEntry::SpaceBefore(a, _) => a.remove_spaces(arena),
PackageEntry::SpaceAfter(a, _) => a.remove_spaces(arena),
package_or_path: self.package_or_path.remove_spaces(arena),
}
}
}
@ -328,8 +314,6 @@ impl<'a> RemoveSpaces<'a> for ImportsEntry<'a> {
match *self {
ImportsEntry::Module(a, b) => ImportsEntry::Module(a, b.remove_spaces(arena)),
ImportsEntry::Package(a, b, c) => ImportsEntry::Package(a, b, c.remove_spaces(arena)),
ImportsEntry::SpaceBefore(a, _) => a.remove_spaces(arena),
ImportsEntry::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}

View file

@ -1,7 +1,7 @@
app "multi-dep-str"
packages { base: "platform" }
packages { pf: "platform" }
imports [ Dep1 ]
provides [ main ] to base
provides [ main ] to pf
main : Str
main = Dep1.str1

View file

@ -1,7 +1,7 @@
app "multi-dep-thunk"
packages { base: "platform" }
packages { pf: "platform" }
imports [ Dep1 ]
provides [ main ] to base
provides [ main ] to pf
main : Str
main = Dep1.value1 {}

11
cli_utils/Cargo.lock generated
View file

@ -1310,13 +1310,13 @@ dependencies = [
name = "inkwell"
version = "0.1.0"
dependencies = [
"inkwell 0.1.0 (git+https://github.com/rtfeldman/inkwell?tag=llvm12-0.release8)",
"inkwell 0.1.0 (git+https://github.com/rtfeldman/inkwell?tag=llvm13-0.release1)",
]
[[package]]
name = "inkwell"
version = "0.1.0"
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm12-0.release8#14b78d96d2dbc95694e181be66e4cd53df3fc02f"
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm13-0.release1#e15d665227b2acad4ca949820d80048e09f3f4e5"
dependencies = [
"either",
"inkwell_internals",
@ -1324,13 +1324,12 @@ dependencies = [
"llvm-sys",
"once_cell",
"parking_lot",
"regex",
]
[[package]]
name = "inkwell_internals"
version = "0.3.0"
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm12-0.release8#14b78d96d2dbc95694e181be66e4cd53df3fc02f"
version = "0.5.0"
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm13-0.release1#e15d665227b2acad4ca949820d80048e09f3f4e5"
dependencies = [
"proc-macro2",
"quote",
@ -2666,6 +2665,7 @@ dependencies = [
"roc_collections",
"roc_module",
"roc_mono",
"roc_reporting",
"roc_std",
"target-lexicon",
]
@ -2679,6 +2679,7 @@ dependencies = [
"roc_collections",
"roc_module",
"roc_mono",
"roc_reporting",
"roc_std",
]

View file

@ -446,9 +446,6 @@ drop : List elem, Nat -> List elem
## To replace the element at a given index, instead of dropping it, see [List.set].
dropAt : List elem, Nat -> List elem
## Drops the last element in a List.
dropLast : List elem -> List elem
## Adds a new element to the end of the list.
##
## >>> List.append [ "a", "b" ] "c"
@ -685,8 +682,6 @@ startsWith : List elem, List elem -> Bool
endsWith : List elem, List elem -> Bool
all : List elem, (elem -> Bool) -> Bool
## Run the given predicate on each element of the list, returning `True` if
## any of the elements satisfy it.
any : List elem, (elem -> Bool) -> Bool
@ -698,3 +693,11 @@ all : List elem, (elem -> Bool) -> Bool
## Returns the first element of the list satisfying a predicate function.
## If no satisfying element is found, an `Err NotFound` is returned.
find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*
## Apply a function that returns a Result on a list, only successful
## Results are kept and returned unwrapped.
keepOks : List before, (before -> Result after *) -> List after
## Apply a function that returns a Result on a list, only unsuccessful
## Results are kept and returned unwrapped.
keepErrs : List before, (before -> Result * after) -> List after

View file

@ -621,13 +621,13 @@ toStr : Num * -> Str
## Examples:
##
## In some countries (e.g. USA and UK), a comma is used to separate thousands:
## >>> Num.format 1_000_000 { base: Decimal, wholeSep: { mark: ",", places: 3 } }
## >>> Num.format 1_000_000 { pf: Decimal, wholeSep: { mark: ",", places: 3 } }
##
## Sometimes when rendering bits, it's nice to group them into groups of 4:
## >>> Num.format 1_000_000 { base: Binary, wholeSep: { mark: " ", places: 4 } }
## >>> Num.format 1_000_000 { pf: Binary, wholeSep: { mark: " ", places: 4 } }
##
## It's also common to render hexadecimal in groups of 2:
## >>> Num.format 1_000_000 { base: Hexadecimal, wholeSep: { mark: " ", places: 2 } }
## >>> Num.format 1_000_000 { pf: Hexadecimal, wholeSep: { mark: " ", places: 2 } }
format :
Num *,
{

View file

@ -2,6 +2,8 @@ interface Result
exposes
[
Result,
isOk,
isErr,
map,
mapErr,
withDefault,
@ -13,6 +15,16 @@ interface Result
## okay, or else there was an error of some sort.
Result ok err : [ @Result ok err ]
## Return True if the result indicates a success, else return False
##
## >>> Result.isOk (Ok 5)
isOk : Result * * -> bool
## Return True if the result indicates a failure, else return False
##
## >>> Result.isErr (Err "uh oh")
isErr : Result * * -> bool
## If the result is `Ok`, return the value it holds. Otherwise, return
## the given default value.
##

View file

@ -29,6 +29,8 @@ isEmpty : Set * -> Bool
len : Set * -> Nat
## Modify
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `add : Set 'elem, 'elem -> Set 'elem`
## Make sure never to add a *NaN* to a [Set]! Because *NaN* is defined to be
@ -41,6 +43,8 @@ add : Set elem, elem -> Set elem
# Original signature: `drop : Set 'elem, 'elem -> Set 'elem`
drop : Set elem, elem -> Set elem
## Transform
## Convert each element in the set to something new, by calling a conversion
## function on each of them. Then return a new set of the converted values.
##

View file

@ -1,6 +1,6 @@
use crate::def::Def;
use crate::expr::{ClosureData, Expr::*};
use crate::expr::{Expr, Field, Recursive, WhenBranch};
use crate::expr::{self, ClosureData, Expr::*};
use crate::expr::{Expr, Field, Recursive};
use crate::pattern::Pattern;
use roc_collections::all::SendMap;
use roc_module::called_via::CalledVia;
@ -3091,7 +3091,7 @@ fn list_keep_errs(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListKeepErrs, var_store)
}
/// List.keepErrs: List before, (before -> Result * after) -> List after
/// List.range: Int a, Int a -> List (Int a)
fn list_range(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListRange, var_store)
}
@ -4107,7 +4107,7 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
)],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(ok),
guard: None,
@ -4137,7 +4137,7 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
)],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(err),
guard: None,
@ -4204,7 +4204,7 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
)],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(ok),
guard: None,
@ -4234,7 +4234,7 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
)],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(err),
guard: None,
@ -4277,7 +4277,7 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![(ret_var, no_region(Pattern::Identifier(Symbol::ARG_3)))],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(Var(Symbol::ARG_3)),
guard: None,
@ -4297,7 +4297,7 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![(var_store.fresh(), no_region(Pattern::Underscore))],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(Var(Symbol::ARG_2)),
guard: None,
@ -4347,7 +4347,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(false_expr),
guard: None,
@ -4374,7 +4374,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(true_expr),
guard: None,
@ -4424,7 +4424,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(true_expr),
guard: None,
@ -4451,7 +4451,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(false_expr),
guard: None,
@ -4513,7 +4513,7 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def {
)],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(ok),
guard: None,
@ -4543,7 +4543,7 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def {
)],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(err),
guard: None,

View file

@ -14,9 +14,8 @@ use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Located, Region};
use roc_types::subs::Variable;
use roc_types::types::AnnotationSource::{self, *};
use roc_types::types::Type::{self, *};
use roc_types::types::{Category, PReason, Reason, RecordField};
use roc_types::types::{AnnotationSource, Category, PReason, Reason, RecordField};
/// This is for constraining Defs
#[derive(Default, Debug)]
@ -604,7 +603,7 @@ pub fn constrain_expr(
FromAnnotation(
name.clone(),
*arity,
TypedWhenBranch {
AnnotationSource::TypedWhenBranch {
index: Index::zero_based(index),
region: ann_source.region(),
},

View file

@ -40,12 +40,12 @@ pub enum Newlines {
No,
}
pub trait Formattable<'a> {
pub trait Formattable {
fn is_multiline(&self) -> bool;
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
@ -53,23 +53,23 @@ pub trait Formattable<'a> {
self.format(buf, indent);
}
fn format(&self, buf: &mut Buf<'a>, indent: u16) {
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
self.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
}
}
/// A reference to a formattable value is also formattable
impl<'a, T> Formattable<'a> for &'a T
impl<'a, T> Formattable for &'a T
where
T: Formattable<'a>,
T: Formattable,
{
fn is_multiline(&self) -> bool {
(*self).is_multiline()
}
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
@ -77,23 +77,23 @@ where
(*self).format_with_options(buf, parens, newlines, indent)
}
fn format(&self, buf: &mut Buf<'a>, indent: u16) {
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
(*self).format(buf, indent)
}
}
/// A Located formattable value is also formattable
impl<'a, T> Formattable<'a> for Located<T>
impl<T> Formattable for Located<T>
where
T: Formattable<'a>,
T: Formattable,
{
fn is_multiline(&self) -> bool {
self.value.is_multiline()
}
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
@ -102,12 +102,12 @@ where
.format_with_options(buf, parens, newlines, indent)
}
fn format(&self, buf: &mut Buf<'a>, indent: u16) {
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
self.value.format(buf, indent)
}
}
impl<'a> Formattable<'a> for TypeAnnotation<'a> {
impl<'a> Formattable for TypeAnnotation<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::TypeAnnotation::*;
@ -148,9 +148,9 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
}
}
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
@ -275,14 +275,14 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
/// > term: { x: 100, y: True }
///
/// So we need two instances, each having the specific separator
impl<'a> Formattable<'a> for AssignedField<'a, TypeAnnotation<'a>> {
impl<'a> Formattable for AssignedField<'a, TypeAnnotation<'a>> {
fn is_multiline(&self) -> bool {
is_multiline_assigned_field_help(self)
}
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
@ -292,14 +292,14 @@ impl<'a> Formattable<'a> for AssignedField<'a, TypeAnnotation<'a>> {
}
}
impl<'a> Formattable<'a> for AssignedField<'a, Expr<'a>> {
impl<'a> Formattable for AssignedField<'a, Expr<'a>> {
fn is_multiline(&self) -> bool {
is_multiline_assigned_field_help(self)
}
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
@ -309,7 +309,7 @@ impl<'a> Formattable<'a> for AssignedField<'a, Expr<'a>> {
}
}
fn is_multiline_assigned_field_help<'a, T: Formattable<'a>>(afield: &AssignedField<'a, T>) -> bool {
fn is_multiline_assigned_field_help<T: Formattable>(afield: &AssignedField<'_, T>) -> bool {
use self::AssignedField::*;
match afield {
@ -322,15 +322,15 @@ fn is_multiline_assigned_field_help<'a, T: Formattable<'a>>(afield: &AssignedFie
}
}
fn format_assigned_field_help<'a, T>(
fn format_assigned_field_help<'a, 'buf, T>(
zelf: &AssignedField<'a, T>,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
parens: Parens,
indent: u16,
separator_prefix: &str,
is_multiline: bool,
) where
T: Formattable<'a>,
T: Formattable,
{
use self::AssignedField::*;
@ -403,7 +403,7 @@ fn format_assigned_field_help<'a, T>(
}
}
impl<'a> Formattable<'a> for Tag<'a> {
impl<'a> Formattable for Tag<'a> {
fn is_multiline(&self) -> bool {
use self::Tag::*;
@ -416,9 +416,9 @@ impl<'a> Formattable<'a> for Tag<'a> {
}
}
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
_parens: Parens,
_newlines: Newlines,
indent: u16,

View file

@ -6,15 +6,15 @@ use crate::{
Buf,
};
pub fn fmt_collection<'a, 'b, T: ExtractSpaces<'a> + Formattable<'b>>(
buf: &mut Buf<'b>,
pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
buf: &mut Buf<'buf>,
indent: u16,
start: char,
end: char,
items: Collection<'a, T>,
newline: Newlines,
) where
<T as ExtractSpaces<'a>>::Item: Formattable<'b>,
<T as ExtractSpaces<'a>>::Item: Formattable,
{
buf.indent(indent);
let is_multiline =

View file

@ -6,7 +6,7 @@ use roc_parse::ast::{Def, Expr, Pattern};
use roc_region::all::Located;
/// A Located formattable value is also formattable
impl<'a> Formattable<'a> for Def<'a> {
impl<'a> Formattable for Def<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::Def::*;
@ -25,9 +25,9 @@ impl<'a> Formattable<'a> for Def<'a> {
}
}
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
@ -107,8 +107,8 @@ impl<'a> Formattable<'a> for Def<'a> {
}
}
fn fmt_expect<'a>(
buf: &mut Buf<'a>,
fn fmt_expect<'a, 'buf>(
buf: &mut Buf<'buf>,
condition: &'a Located<Expr<'a>>,
is_multiline: bool,
indent: u16,
@ -123,11 +123,16 @@ fn fmt_expect<'a>(
condition.format(buf, return_indent);
}
pub fn fmt_def<'a>(buf: &mut Buf<'a>, def: &Def<'a>, indent: u16) {
pub fn fmt_def<'a, 'buf>(buf: &mut Buf<'buf>, def: &Def<'a>, indent: u16) {
def.format(buf, indent);
}
pub fn fmt_body<'a>(buf: &mut Buf<'a>, pattern: &'a Pattern<'a>, body: &'a Expr<'a>, indent: u16) {
pub fn fmt_body<'a, 'buf>(
buf: &mut Buf<'buf>,
pattern: &'a Pattern<'a>,
body: &'a Expr<'a>,
indent: u16,
) {
pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);
buf.push_str(" =");
if body.is_multiline() {

View file

@ -11,7 +11,7 @@ use roc_parse::ast::{
use roc_parse::ast::{StrLiteral, StrSegment};
use roc_region::all::Located;
impl<'a> Formattable<'a> for Expr<'a> {
impl<'a> Formattable for Expr<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::Expr::*;
// TODO cache these answers using a Map<Pointer, bool>, so
@ -106,9 +106,9 @@ impl<'a> Formattable<'a> for Expr<'a> {
}
}
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
@ -289,7 +289,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
}
}
fn format_str_segment<'a>(seg: &StrSegment<'a>, buf: &mut Buf<'a>, indent: u16) {
fn format_str_segment<'a, 'buf>(seg: &StrSegment<'a>, buf: &mut Buf<'buf>, indent: u16) {
use StrSegment::*;
match seg {
@ -344,7 +344,7 @@ fn push_op(buf: &mut Buf, op: BinOp) {
}
}
pub fn fmt_str_literal<'a>(buf: &mut Buf<'a>, literal: StrLiteral<'a>, indent: u16) {
pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u16) {
use roc_parse::ast::StrLiteral::*;
buf.indent(indent);
@ -395,8 +395,8 @@ pub fn fmt_str_literal<'a>(buf: &mut Buf<'a>, literal: StrLiteral<'a>, indent: u
buf.push('"');
}
fn fmt_bin_ops<'a>(
buf: &mut Buf<'a>,
fn fmt_bin_ops<'a, 'buf>(
buf: &mut Buf<'buf>,
lefts: &'a [(Located<Expr<'a>>, Located<BinOp>)],
loc_right_side: &'a Located<Expr<'a>>,
part_of_multi_line_bin_ops: bool,
@ -454,8 +454,8 @@ fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool {
}
}
fn fmt_when<'a>(
buf: &mut Buf<'a>,
fn fmt_when<'a, 'buf>(
buf: &mut Buf<'buf>,
loc_condition: &'a Located<Expr<'a>>,
branches: &[&'a WhenBranch<'a>],
indent: u16,
@ -569,8 +569,8 @@ fn fmt_when<'a>(
}
}
fn fmt_expect<'a>(
buf: &mut Buf<'a>,
fn fmt_expect<'a, 'buf>(
buf: &mut Buf<'buf>,
condition: &'a Located<Expr<'a>>,
continuation: &'a Located<Expr<'a>>,
is_multiline: bool,
@ -588,8 +588,8 @@ fn fmt_expect<'a>(
continuation.format(buf, return_indent);
}
fn fmt_if<'a>(
buf: &mut Buf<'a>,
fn fmt_if<'a, 'buf>(
buf: &mut Buf<'buf>,
branches: &'a [(Located<Expr<'a>>, Located<Expr<'a>>)],
final_else: &'a Located<Expr<'a>>,
is_multiline: bool,
@ -709,8 +709,8 @@ fn fmt_if<'a>(
final_else.format(buf, return_indent);
}
fn fmt_closure<'a>(
buf: &mut Buf<'a>,
fn fmt_closure<'a, 'buf>(
buf: &mut Buf<'buf>,
loc_patterns: &'a [Located<Pattern<'a>>],
loc_ret: &'a Located<Expr<'a>>,
indent: u16,
@ -782,8 +782,8 @@ fn fmt_closure<'a>(
loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent);
}
fn fmt_backpassing<'a>(
buf: &mut Buf<'a>,
fn fmt_backpassing<'a, 'buf>(
buf: &mut Buf<'buf>,
loc_patterns: &'a [Located<Pattern<'a>>],
loc_body: &'a Located<Expr<'a>>,
loc_ret: &'a Located<Expr<'a>>,
@ -876,8 +876,8 @@ fn pattern_needs_parens_when_backpassing(pat: &Pattern) -> bool {
}
}
fn fmt_record<'a>(
buf: &mut Buf<'a>,
fn fmt_record<'a, 'buf>(
buf: &mut Buf<'buf>,
update: Option<&'a Located<Expr<'a>>>,
fields: Collection<'a, Located<AssignedField<'a, Expr<'a>>>>,
indent: u16,
@ -945,13 +945,13 @@ fn fmt_record<'a>(
}
}
fn format_field_multiline<'a, T>(
buf: &mut Buf<'a>,
fn format_field_multiline<'a, 'buf, T>(
buf: &mut Buf<'buf>,
field: &AssignedField<'a, T>,
indent: u16,
separator_prefix: &str,
) where
T: Formattable<'a>,
T: Formattable,
{
use self::AssignedField::*;
match field {

View file

@ -3,14 +3,14 @@ use crate::collection::fmt_collection;
use crate::expr::fmt_str_literal;
use crate::spaces::{fmt_default_spaces, fmt_spaces, INDENT};
use crate::Buf;
use roc_parse::ast::{Collection, Module};
use roc_parse::ast::{Collection, Module, Spaced};
use roc_parse::header::{
AppHeader, Effects, ExposesEntry, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageName, PackageOrPath, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent,
};
use roc_region::all::Located;
pub fn fmt_module<'a>(buf: &mut Buf<'a>, module: &'a Module<'a>) {
pub fn fmt_module<'a, 'buf>(buf: &mut Buf<'buf>, module: &'a Module<'a>) {
match module {
Module::Interface { header } => {
fmt_interface_header(buf, header);
@ -24,7 +24,7 @@ pub fn fmt_module<'a>(buf: &mut Buf<'a>, module: &'a Module<'a>) {
}
}
pub fn fmt_interface_header<'a>(buf: &mut Buf<'a>, header: &'a InterfaceHeader<'a>) {
pub fn fmt_interface_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a InterfaceHeader<'a>) {
let indent = INDENT;
buf.indent(0);
@ -49,7 +49,7 @@ pub fn fmt_interface_header<'a>(buf: &mut Buf<'a>, header: &'a InterfaceHeader<'
fmt_imports(buf, header.imports, indent);
}
pub fn fmt_app_header<'a>(buf: &mut Buf<'a>, header: &'a AppHeader<'a>) {
pub fn fmt_app_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a AppHeader<'a>) {
let indent = INDENT;
buf.indent(0);
buf.push_str("app");
@ -84,7 +84,7 @@ pub fn fmt_app_header<'a>(buf: &mut Buf<'a>, header: &'a AppHeader<'a>) {
fmt_to(buf, header.to.value, indent);
}
pub fn fmt_platform_header<'a>(buf: &mut Buf<'a>, header: &'a PlatformHeader<'a>) {
pub fn fmt_platform_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a PlatformHeader<'a>) {
let indent = INDENT;
buf.indent(0);
@ -131,15 +131,15 @@ pub fn fmt_platform_header<'a>(buf: &mut Buf<'a>, header: &'a PlatformHeader<'a>
fmt_effects(buf, &header.effects, indent);
}
fn fmt_requires<'a>(buf: &mut Buf<'a>, requires: &PlatformRequires<'a>, indent: u16) {
fn fmt_requires<'a, 'buf>(buf: &mut Buf<'buf>, requires: &PlatformRequires<'a>, indent: u16) {
fmt_collection(buf, indent, '{', '}', requires.rigids, Newlines::No);
buf.push_str(" { ");
fmt_typed_ident(buf, &requires.signature.value, indent);
requires.signature.value.format(buf, indent);
buf.push_str(" }");
}
fn fmt_effects<'a>(buf: &mut Buf<'a>, effects: &Effects<'a>, indent: u16) {
fn fmt_effects<'a, 'buf>(buf: &mut Buf<'buf>, effects: &Effects<'a>, indent: u16) {
fmt_default_spaces(buf, effects.spaces_before_effects_keyword, " ", indent);
buf.indent(indent);
buf.push_str("effects");
@ -155,95 +155,84 @@ fn fmt_effects<'a>(buf: &mut Buf<'a>, effects: &Effects<'a>, indent: u16) {
fmt_collection(buf, indent, '{', '}', effects.entries, Newlines::No)
}
fn fmt_typed_ident<'a>(buf: &mut Buf<'a>, entry: &TypedIdent<'a>, indent: u16) {
use TypedIdent::*;
match entry {
Entry {
ident,
spaces_before_colon,
ann,
} => {
impl<'a> Formattable for TypedIdent<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
buf.indent(indent);
buf.push_str(ident.value);
fmt_default_spaces(buf, spaces_before_colon, " ", indent);
buf.push_str(self.ident.value);
fmt_default_spaces(buf, self.spaces_before_colon, " ", indent);
buf.push_str(": ");
ann.value.format(buf, indent);
}
SpaceBefore(sub_entry, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
fmt_typed_ident(buf, sub_entry, indent);
}
SpaceAfter(sub_entry, spaces) => {
fmt_typed_ident(buf, sub_entry, indent);
fmt_spaces(buf, spaces.iter(), indent);
}
}
}
impl<'a> Formattable<'a> for TypedIdent<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format(&self, buf: &mut Buf<'a>, indent: u16) {
fmt_typed_ident(buf, self, indent);
self.ann.value.format(buf, indent);
}
}
impl<'a> Formattable<'a> for PlatformRigid<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format(&self, buf: &mut Buf<'a>, indent: u16) {
fmt_platform_rigid(buf, self, indent);
}
}
fn fmt_package_name<'a>(buf: &mut Buf<'a>, name: PackageName) {
fn fmt_package_name<'buf>(buf: &mut Buf<'buf>, name: PackageName) {
buf.push_str(name.account);
buf.push('/');
buf.push_str(name.pkg);
}
fn fmt_platform_rigid<'a>(buf: &mut Buf<'a>, entry: &PlatformRigid<'a>, indent: u16) {
use roc_parse::header::PlatformRigid::*;
impl<'a, T: Formattable> Formattable for Spaced<'a, T> {
fn is_multiline(&self) -> bool {
// TODO
false
}
match entry {
Entry { rigid, alias } => {
buf.push_str(rigid);
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
parens: crate::annotation::Parens,
newlines: Newlines,
indent: u16,
) {
match self {
Spaced::Item(item) => {
item.format_with_options(buf, parens, newlines, indent);
}
Spaced::SpaceBefore(item, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
item.format_with_options(buf, parens, newlines, indent);
}
Spaced::SpaceAfter(item, spaces) => {
item.format_with_options(buf, parens, newlines, indent);
fmt_spaces(buf, spaces.iter(), indent);
}
}
}
}
impl<'a> Formattable for PlatformRigid<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format<'buf>(&self, buf: &mut Buf<'buf>, _indent: u16) {
buf.push_str(self.rigid);
buf.push_str("=>");
buf.push_str(alias);
}
SpaceBefore(sub_entry, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
fmt_platform_rigid(buf, sub_entry, indent);
}
SpaceAfter(sub_entry, spaces) => {
fmt_platform_rigid(buf, sub_entry, indent);
fmt_spaces(buf, spaces.iter(), indent);
}
buf.push_str(self.alias);
}
}
fn fmt_imports<'a>(
buf: &mut Buf<'a>,
loc_entries: Collection<'a, Located<ImportsEntry<'a>>>,
fn fmt_imports<'a, 'buf>(
buf: &mut Buf<'buf>,
loc_entries: Collection<'a, Located<Spaced<'a, ImportsEntry<'a>>>>,
indent: u16,
) {
fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No)
}
fn fmt_provides<'a>(
buf: &mut Buf<'a>,
loc_entries: Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
fn fmt_provides<'a, 'buf>(
buf: &mut Buf<'buf>,
loc_entries: Collection<'a, Located<Spaced<'a, ExposedName<'a>>>>,
indent: u16,
) {
fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No)
}
fn fmt_to<'a>(buf: &mut Buf<'a>, to: To<'a>, indent: u16) {
fn fmt_to<'buf>(buf: &mut Buf<'buf>, to: To, indent: u16) {
match to {
To::ExistingPackage(name) => {
buf.push_str(name);
@ -252,113 +241,95 @@ fn fmt_to<'a>(buf: &mut Buf<'a>, to: To<'a>, indent: u16) {
}
}
fn fmt_exposes<'a, N: FormatName + Copy + 'a>(
buf: &mut Buf<'a>,
loc_entries: Collection<'_, Located<ExposesEntry<'_, N>>>,
fn fmt_exposes<'buf, N: Formattable + Copy>(
buf: &mut Buf<'buf>,
loc_entries: Collection<'_, Located<Spaced<'_, N>>>,
indent: u16,
) {
fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No)
}
impl<'a, 'b, N: FormatName> Formattable<'a> for ExposesEntry<'b, N> {
fn is_multiline(&self) -> bool {
false
}
fn format(&self, buf: &mut Buf<'a>, indent: u16) {
fmt_exposes_entry(buf, self, indent);
}
}
pub trait FormatName {
fn format<'a>(&self, buf: &mut Buf<'a>);
fn format<'buf>(&self, buf: &mut Buf<'buf>);
}
impl<'a> FormatName for &'a str {
fn format<'b>(&self, buf: &mut Buf<'b>) {
fn format<'buf>(&self, buf: &mut Buf<'buf>) {
buf.push_str(self)
}
}
impl<'a> FormatName for ModuleName<'a> {
fn format<'b>(&self, buf: &mut Buf<'b>) {
fn format<'buf>(&self, buf: &mut Buf<'buf>) {
buf.push_str(self.as_str());
}
}
fn fmt_exposes_entry<'a, 'b, N: FormatName>(
buf: &mut Buf<'a>,
entry: &ExposesEntry<'b, N>,
indent: u16,
) {
use roc_parse::header::ExposesEntry::*;
match entry {
Exposed(ident) => ident.format(buf),
SpaceBefore(sub_entry, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
fmt_exposes_entry(buf, sub_entry, indent);
}
SpaceAfter(sub_entry, spaces) => {
fmt_exposes_entry(buf, sub_entry, indent);
fmt_spaces(buf, spaces.iter(), indent);
impl<'a> Formattable for ModuleName<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format<'buf>(&self, buf: &mut Buf<'buf>, _indent: u16) {
buf.push_str(self.as_str());
}
}
fn fmt_packages<'a>(
buf: &mut Buf<'a>,
loc_entries: Collection<'a, Located<PackageEntry<'a>>>,
impl<'a> Formattable for ExposedName<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format<'buf>(&self, buf: &mut Buf<'buf>, _indent: u16) {
buf.push_str(self.as_str());
}
}
impl<'a> FormatName for ExposedName<'a> {
fn format<'buf>(&self, buf: &mut Buf<'buf>) {
buf.push_str(self.as_str());
}
}
fn fmt_packages<'a, 'buf>(
buf: &mut Buf<'buf>,
loc_entries: Collection<'a, Located<Spaced<'a, PackageEntry<'a>>>>,
indent: u16,
) {
fmt_collection(buf, indent, '{', '}', loc_entries, Newlines::No)
}
impl<'a> Formattable<'a> for PackageEntry<'a> {
impl<'a> Formattable for PackageEntry<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format(&self, buf: &mut Buf<'a>, indent: u16) {
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
fmt_packages_entry(buf, self, indent);
}
}
impl<'a> Formattable<'a> for ImportsEntry<'a> {
impl<'a> Formattable for ImportsEntry<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format(&self, buf: &mut Buf<'a>, indent: u16) {
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
fmt_imports_entry(buf, self, indent);
}
}
fn fmt_packages_entry<'a>(buf: &mut Buf<'a>, entry: &PackageEntry<'a>, indent: u16) {
use PackageEntry::*;
match entry {
Entry {
shorthand,
spaces_after_shorthand,
package_or_path,
} => {
buf.push_str(shorthand);
fn fmt_packages_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &PackageEntry<'a>, indent: u16) {
buf.push_str(entry.shorthand);
buf.push(':');
fmt_default_spaces(buf, spaces_after_shorthand, " ", indent);
fmt_package_or_path(buf, &package_or_path.value, indent);
}
SpaceBefore(sub_entry, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
fmt_packages_entry(buf, sub_entry, indent);
}
SpaceAfter(sub_entry, spaces) => {
fmt_packages_entry(buf, sub_entry, indent);
fmt_spaces(buf, spaces.iter(), indent);
}
}
fmt_default_spaces(buf, entry.spaces_after_shorthand, " ", indent);
fmt_package_or_path(buf, &entry.package_or_path.value, indent);
}
fn fmt_package_or_path<'a>(buf: &mut Buf<'a>, package_or_path: &PackageOrPath<'a>, indent: u16) {
fn fmt_package_or_path<'a, 'buf>(
buf: &mut Buf<'buf>,
package_or_path: &PackageOrPath<'a>,
indent: u16,
) {
match package_or_path {
PackageOrPath::Package(_name, _version) => {
todo!("format package");
@ -367,7 +338,7 @@ fn fmt_package_or_path<'a>(buf: &mut Buf<'a>, package_or_path: &PackageOrPath<'a
}
}
fn fmt_imports_entry<'a>(buf: &mut Buf<'a>, entry: &ImportsEntry<'a>, indent: u16) {
fn fmt_imports_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &ImportsEntry<'a>, indent: u16) {
use roc_parse::header::ImportsEntry::*;
match entry {
@ -392,14 +363,5 @@ fn fmt_imports_entry<'a>(buf: &mut Buf<'a>, entry: &ImportsEntry<'a>, indent: u1
fmt_collection(buf, indent, '{', '}', *entries, Newlines::No)
}
}
SpaceBefore(sub_entry, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
fmt_imports_entry(buf, sub_entry, indent);
}
SpaceAfter(sub_entry, spaces) => {
fmt_imports_entry(buf, sub_entry, indent);
fmt_spaces(buf, spaces.iter(), indent);
}
}
}

View file

@ -3,11 +3,16 @@ use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt};
use crate::Buf;
use roc_parse::ast::{Base, Pattern};
pub fn fmt_pattern<'a>(buf: &mut Buf<'a>, pattern: &'a Pattern<'a>, indent: u16, parens: Parens) {
pub fn fmt_pattern<'a, 'buf>(
buf: &mut Buf<'buf>,
pattern: &'a Pattern<'a>,
indent: u16,
parens: Parens,
) {
pattern.format_with_options(buf, parens, Newlines::No, indent);
}
impl<'a> Formattable<'a> for Pattern<'a> {
impl<'a> Formattable for Pattern<'a> {
fn is_multiline(&self) -> bool {
// Theory: a pattern should only be multiline when it contains a comment
match self {
@ -37,9 +42,9 @@ impl<'a> Formattable<'a> for Pattern<'a> {
}
}
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,

View file

@ -18,8 +18,8 @@ pub fn add_spaces(buf: &mut String<'_>, spaces: u16) {
}
}
pub fn fmt_default_spaces<'a>(
buf: &mut Buf<'a>,
pub fn fmt_default_spaces<'a, 'buf>(
buf: &mut Buf<'buf>,
spaces: &[CommentOrNewline<'a>],
default: &str,
indent: u16,
@ -31,9 +31,9 @@ pub fn fmt_default_spaces<'a>(
}
}
pub fn fmt_spaces<'a, 'b, I>(buf: &mut Buf<'a>, spaces: I, indent: u16)
pub fn fmt_spaces<'a, 'buf, I>(buf: &mut Buf<'buf>, spaces: I, indent: u16)
where
I: Iterator<Item = &'b CommentOrNewline<'b>>,
I: Iterator<Item = &'a CommentOrNewline<'a>>,
{
use self::CommentOrNewline::*;
@ -84,13 +84,13 @@ pub enum NewlineAt {
/// The `new_line_at` argument describes how new lines should be inserted
/// at the beginning or at the end of the block
/// in the case of there is some comment in the `spaces` argument.
pub fn fmt_comments_only<'a, 'b, I>(
buf: &mut Buf<'a>,
pub fn fmt_comments_only<'a, 'buf, I>(
buf: &mut Buf<'buf>,
spaces: I,
new_line_at: NewlineAt,
indent: u16,
) where
I: Iterator<Item = &'b CommentOrNewline<'b>>,
I: Iterator<Item = &'a CommentOrNewline<'a>>,
{
use self::CommentOrNewline::*;
use NewlineAt::*;
@ -123,7 +123,7 @@ pub fn fmt_comments_only<'a, 'b, I>(
}
}
fn fmt_comment<'a>(buf: &mut Buf<'a>, comment: &str) {
fn fmt_comment<'buf>(buf: &mut Buf<'buf>, comment: &str) {
buf.push('#');
if !comment.starts_with(' ') {
buf.push(' ');
@ -131,7 +131,7 @@ fn fmt_comment<'a>(buf: &mut Buf<'a>, comment: &str) {
buf.push_str(comment);
}
fn fmt_docs<'a>(buf: &mut Buf<'a>, docs: &str) {
fn fmt_docs<'buf>(buf: &mut Buf<'buf>, docs: &str) {
buf.push_str("##");
if !docs.starts_with(' ') {
buf.push(' ');

View file

@ -2625,7 +2625,7 @@ mod test_fmt {
fn single_line_app() {
module_formats_same(indoc!(
r#"
app "Foo" packages { base: "platform" } imports [] provides [ main ] to base"#
app "Foo" packages { pf: "platform" } imports [] provides [ main ] to pf"#
));
}

View file

@ -163,7 +163,7 @@ trait Backend<'a> {
.push((rc_proc_symbol, rc_proc_layout));
}
self.build_stmt(&rc_stmt, ret_layout)
self.build_stmt(rc_stmt, ret_layout)
}
Stmt::Switch {
cond_symbol,

View file

@ -10,6 +10,7 @@ edition = "2018"
roc_collections = { path = "../collections" }
roc_module = { path = "../module" }
roc_builtins = { path = "../builtins" }
roc_reporting = { path = "../../reporting" }
roc_mono = { path = "../mono" }
roc_std = { path = "../../roc_std" }
morphic_lib = { path = "../../vendor/morphic_lib" }

View file

@ -62,6 +62,7 @@ use roc_mono::ir::{
ModifyRc, OptLevel, ProcLayout,
};
use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds, TagIdIntType, UnionLayout};
use roc_reporting::internal_error;
use target_lexicon::{Architecture, OperatingSystem, Triple};
/// This is for Inkwell's FunctionValue::verify - we want to know the verification
@ -1757,8 +1758,8 @@ fn tag_pointer_set_tag_id<'a, 'ctx, 'env>(
tag_id: u8,
pointer: PointerValue<'ctx>,
) -> PointerValue<'ctx> {
// we only have 3 bits, so can encode only 0..7
debug_assert!(tag_id < 8);
// we only have 3 bits, so can encode only 0..7 (or on 32-bit targets, 2 bits to encode 0..3)
debug_assert!((tag_id as u32) < env.ptr_bytes);
let ptr_int = env.ptr_int();
@ -2217,6 +2218,24 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>(
data_ptr
}
macro_rules! dict_key_value_layout {
($dict_layout:expr) => {
match $dict_layout {
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => (key_layout, value_layout),
_ => unreachable!("invalid dict layout"),
}
};
}
macro_rules! list_element_layout {
($list_layout:expr) => {
match $list_layout {
Layout::Builtin(Builtin::List(list_layout)) => *list_layout,
_ => unreachable!("invalid list layout"),
}
};
}
fn list_literal<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
@ -5443,7 +5462,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
list_reverse(env, list, list_layout, update_mode)
let element_layout = list_element_layout!(list_layout);
list_reverse(env, list, element_layout, update_mode)
}
ListConcat => {
debug_assert_eq!(args.len(), 2);
@ -5452,7 +5473,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let second_list = load_symbol(scope, &args[1]);
list_concat(env, parent, first_list, second_list, list_layout)
let element_layout = list_element_layout!(list_layout);
list_concat(env, first_list, second_list, element_layout)
}
ListContains => {
// List.contains : List elem, elem -> Bool
@ -5497,17 +5520,15 @@ fn run_low_level<'a, 'ctx, 'env>(
let index_1 = load_symbol(scope, &args[1]);
let index_2 = load_symbol(scope, &args[2]);
match list_layout {
Layout::Builtin(Builtin::List(element_layout)) => list_swap(
let element_layout = list_element_layout!(list_layout);
list_swap(
env,
original_wrapper,
index_1.into_int_value(),
index_2.into_int_value(),
element_layout,
update_mode,
),
_ => unreachable!("Invalid layout {:?} in List.swap", list_layout),
}
)
}
ListSublist => {
// List.sublist : List elem, { start : Nat, len : Nat } -> List elem
@ -5522,17 +5543,15 @@ fn run_low_level<'a, 'ctx, 'env>(
let start = load_symbol(scope, &args[1]);
let len = load_symbol(scope, &args[2]);
match list_layout {
Layout::Builtin(Builtin::List(element_layout)) => list_sublist(
let element_layout = list_element_layout!(list_layout);
list_sublist(
env,
layout_ids,
original_wrapper,
start.into_int_value(),
len.into_int_value(),
element_layout,
),
_ => unreachable!("Invalid layout {:?} in List.sublist", list_layout),
}
)
}
ListDropAt => {
// List.dropAt : List elem, Nat -> List elem
@ -5543,16 +5562,14 @@ fn run_low_level<'a, 'ctx, 'env>(
let count = load_symbol(scope, &args[1]);
match list_layout {
Layout::Builtin(Builtin::List(element_layout)) => list_drop_at(
let element_layout = list_element_layout!(list_layout);
list_drop_at(
env,
layout_ids,
original_wrapper,
count.into_int_value(),
element_layout,
),
_ => unreachable!("Invalid layout {:?} in List.dropAt", list_layout),
}
)
}
ListPrepend => {
// List.prepend : List elem, elem -> List elem
@ -5569,7 +5586,44 @@ fn run_low_level<'a, 'ctx, 'env>(
let (list, outer_list_layout) = load_symbol_and_layout(scope, &args[0]);
list_join(env, parent, list, outer_list_layout)
let inner_list_layout = list_element_layout!(outer_list_layout);
let element_layout = list_element_layout!(inner_list_layout);
list_join(env, list, element_layout)
}
ListGetUnsafe => {
// List.get : List elem, Nat -> [ Ok elem, OutOfBounds ]*
debug_assert_eq!(args.len(), 2);
let (wrapper_struct, list_layout) = load_symbol_and_layout(scope, &args[0]);
let wrapper_struct = wrapper_struct.into_struct_value();
let elem_index = load_symbol(scope, &args[1]).into_int_value();
let element_layout = list_element_layout!(list_layout);
list_get_unsafe(
env,
layout_ids,
parent,
element_layout,
elem_index,
wrapper_struct,
)
}
ListSet => {
let list = load_symbol(scope, &args[0]);
let index = load_symbol(scope, &args[1]);
let (element, element_layout) = load_symbol_and_layout(scope, &args[2]);
list_set(
env,
layout_ids,
list,
index.into_int_value(),
element,
element_layout,
update_mode,
)
}
NumToStr => {
// Num.toStr : Num a -> Str
@ -5604,9 +5658,13 @@ fn run_low_level<'a, 'ctx, 'env>(
let int_type = convert::int_type_from_int_width(env, *int_width);
build_int_unary_op(env, arg.into_int_value(), int_type, op)
}
Float(float_width) => {
build_float_unary_op(env, arg.into_float_value(), op, *float_width)
}
Float(float_width) => build_float_unary_op(
env,
layout,
arg.into_float_value(),
op,
*float_width,
),
_ => {
unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, arg_layout);
}
@ -5852,41 +5910,6 @@ fn run_low_level<'a, 'ctx, 'env>(
BasicValueEnum::IntValue(bool_val)
}
ListGetUnsafe => {
// List.get : List elem, Nat -> [ Ok elem, OutOfBounds ]*
debug_assert_eq!(args.len(), 2);
let (wrapper_struct, list_layout) = load_symbol_and_layout(scope, &args[0]);
let wrapper_struct = wrapper_struct.into_struct_value();
let elem_index = load_symbol(scope, &args[1]).into_int_value();
list_get_unsafe(
env,
layout_ids,
parent,
list_layout,
elem_index,
wrapper_struct,
)
}
ListSet => {
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
let (index, _) = load_symbol_and_layout(scope, &args[1]);
let (element, _) = load_symbol_and_layout(scope, &args[2]);
match list_layout {
Layout::Builtin(Builtin::List(element_layout)) => list_set(
env,
layout_ids,
list,
index.into_int_value(),
element,
element_layout,
update_mode,
),
_ => unreachable!("invalid dict layout"),
}
}
Hash => {
debug_assert_eq!(args.len(), 2);
let seed = load_symbol(scope, &args[0]);
@ -5916,116 +5939,80 @@ fn run_low_level<'a, 'ctx, 'env>(
debug_assert_eq!(args.len(), 2);
let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (key, key_layout) = load_symbol_and_layout(scope, &args[1]);
let key = load_symbol(scope, &args[1]);
match dict_layout {
Layout::Builtin(Builtin::Dict(_, value_layout)) => {
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_remove(env, layout_ids, dict, key, key_layout, value_layout)
}
_ => unreachable!("invalid dict layout"),
}
}
DictContains => {
debug_assert_eq!(args.len(), 2);
let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (key, key_layout) = load_symbol_and_layout(scope, &args[1]);
let key = load_symbol(scope, &args[1]);
match dict_layout {
Layout::Builtin(Builtin::Dict(_, value_layout)) => {
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_contains(env, layout_ids, dict, key, key_layout, value_layout)
}
_ => unreachable!("invalid dict layout"),
}
}
DictGetUnsafe => {
debug_assert_eq!(args.len(), 2);
let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (key, key_layout) = load_symbol_and_layout(scope, &args[1]);
let key = load_symbol(scope, &args[1]);
match dict_layout {
Layout::Builtin(Builtin::Dict(_, value_layout)) => {
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_get(env, layout_ids, dict, key, key_layout, value_layout)
}
_ => unreachable!("invalid dict layout"),
}
}
DictKeys => {
debug_assert_eq!(args.len(), 1);
let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
match dict_layout {
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_keys(env, layout_ids, dict, key_layout, value_layout)
}
_ => unreachable!("invalid dict layout"),
}
}
DictValues => {
debug_assert_eq!(args.len(), 1);
let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
match dict_layout {
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_values(env, layout_ids, dict, key_layout, value_layout)
}
_ => unreachable!("invalid dict layout"),
}
}
DictUnion => {
debug_assert_eq!(args.len(), 2);
let (dict1, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (dict2, _) = load_symbol_and_layout(scope, &args[1]);
match dict_layout {
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_union(env, layout_ids, dict1, dict2, key_layout, value_layout)
}
_ => unreachable!("invalid dict layout"),
}
}
DictDifference => {
debug_assert_eq!(args.len(), 2);
let (dict1, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (dict2, _) = load_symbol_and_layout(scope, &args[1]);
match dict_layout {
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_difference(env, layout_ids, dict1, dict2, key_layout, value_layout)
}
_ => unreachable!("invalid dict layout"),
}
}
DictIntersection => {
debug_assert_eq!(args.len(), 2);
let (dict1, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (dict2, _) = load_symbol_and_layout(scope, &args[1]);
match dict_layout {
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_intersection(env, layout_ids, dict1, dict2, key_layout, value_layout)
}
_ => unreachable!("invalid dict layout"),
}
}
SetFromList => {
debug_assert_eq!(args.len(), 1);
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
match list_layout {
Layout::Builtin(Builtin::List(key_layout)) => {
let key_layout = list_element_layout!(list_layout);
set_from_list(env, layout_ids, list, key_layout)
}
_ => unreachable!("invalid dict layout"),
}
}
ExpectTrue => {
debug_assert_eq!(args.len(), 1);
@ -6543,17 +6530,6 @@ pub fn build_num_binop<'a, 'ctx, 'env>(
{
use roc_mono::layout::Builtin::*;
let float_binop = |float_width| {
build_float_binop(
env,
parent,
float_width,
lhs_arg.into_float_value(),
rhs_arg.into_float_value(),
op,
)
};
match lhs_builtin {
Int(int_width) => build_int_binop(
env,
@ -6564,7 +6540,14 @@ pub fn build_num_binop<'a, 'ctx, 'env>(
op,
),
Float(float_width) => float_binop(*float_width),
Float(float_width) => build_float_binop(
env,
parent,
*float_width,
lhs_arg.into_float_value(),
rhs_arg.into_float_value(),
op,
),
Decimal => {
build_dec_binop(env, parent, lhs_arg, lhs_layout, rhs_arg, rhs_layout, op)
@ -6983,9 +6966,10 @@ fn int_abs_with_overflow<'a, 'ctx, 'env>(
fn build_float_unary_op<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'a>,
arg: FloatValue<'ctx>,
op: LowLevel,
float_width: FloatWidth,
float_width: FloatWidth, // arg width
) -> BasicValueEnum<'ctx> {
use roc_module::low_level::LowLevel::*;
@ -6997,7 +6981,35 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
NumAbs => env.call_intrinsic(&LLVM_FABS[float_width], &[arg.into()]),
NumSqrtUnchecked => env.call_intrinsic(&LLVM_SQRT[float_width], &[arg.into()]),
NumLogUnchecked => env.call_intrinsic(&LLVM_LOG[float_width], &[arg.into()]),
NumToFloat => arg.into(), /* Converting from Float to Float is a no-op */
NumToFloat => {
let return_width = match layout {
Layout::Builtin(Builtin::Float(return_width)) => *return_width,
_ => internal_error!("Layout for returning is not Float : {:?}", layout),
};
match (float_width, return_width) {
(FloatWidth::F32, FloatWidth::F32) => arg.into(),
(FloatWidth::F32, FloatWidth::F64) => bd.build_cast(
InstructionOpcode::FPExt,
arg,
env.context.f64_type(),
"f32_to_f64",
),
(FloatWidth::F64, FloatWidth::F32) => bd.build_cast(
InstructionOpcode::FPTrunc,
arg,
env.context.f32_type(),
"f64_to_f32",
),
(FloatWidth::F64, FloatWidth::F64) => arg.into(),
(FloatWidth::F128, FloatWidth::F128) => arg.into(),
(FloatWidth::F128, _) => {
unimplemented!("I cannot handle F128 with Num.toFloat yet")
}
(_, FloatWidth::F128) => {
unimplemented!("I cannot handle F128 with Num.toFloat yet")
}
}
}
NumCeiling => env.builder.build_cast(
InstructionOpcode::FPToSI,
env.call_intrinsic(&LLVM_CEILING[float_width], &[arg.into()]),

View file

@ -145,12 +145,9 @@ pub fn list_repeat<'a, 'ctx, 'env>(
/// List.join : List (List elem) -> List elem
pub fn list_join<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
_parent: FunctionValue<'ctx>,
outer_list: BasicValueEnum<'ctx>,
outer_list_layout: &Layout<'a>,
element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
match outer_list_layout {
Layout::Builtin(Builtin::List(Layout::Builtin(Builtin::List(element_layout)))) => {
call_bitcode_fn_returns_list(
env,
&[
@ -161,31 +158,20 @@ pub fn list_join<'a, 'ctx, 'env>(
bitcode::LIST_JOIN,
)
}
_ => {
unreachable!("Invalid List layout for List.join {:?}", outer_list_layout);
}
}
}
/// List.reverse : List elem -> List elem
pub fn list_reverse<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
list: BasicValueEnum<'ctx>,
list_layout: &Layout<'a>,
element_layout: &Layout<'a>,
update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> {
let element_layout = match *list_layout {
Layout::Builtin(Builtin::List(elem_layout)) => *elem_layout,
_ => unreachable!("Invalid layout {:?} in List.reverse", list_layout),
};
call_bitcode_fn_returns_list(
env,
&[
pass_list_cc(env, list),
env.alignment_intvalue(&element_layout),
layout_width(env, &element_layout),
env.alignment_intvalue(element_layout),
layout_width(env, element_layout),
pass_update_mode(env, update_mode),
],
bitcode::LIST_REVERSE,
@ -196,39 +182,28 @@ pub fn list_get_unsafe<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>,
list_layout: &Layout<'a>,
element_layout: &Layout<'a>,
elem_index: IntValue<'ctx>,
wrapper_struct: StructValue<'ctx>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
match list_layout {
Layout::Builtin(Builtin::List(elem_layout)) => {
let elem_type = basic_type_from_layout(env, elem_layout);
let elem_type = basic_type_from_layout(env, element_layout);
let ptr_type = elem_type.ptr_type(AddressSpace::Generic);
// Load the pointer to the array data
let array_data_ptr = load_list_ptr(builder, wrapper_struct, ptr_type);
// Assume the bounds have already been checked earlier
// (e.g. by List.get or List.first, which wrap List.#getUnsafe)
let elem_ptr = unsafe {
builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "list_get_element")
};
let elem_ptr =
unsafe { builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "list_get_element") };
let result = load_roc_value(env, **elem_layout, elem_ptr, "list_get_load_element");
let result = load_roc_value(env, *element_layout, elem_ptr, "list_get_load_element");
increment_refcount_layout(env, parent, layout_ids, 1, result, elem_layout);
increment_refcount_layout(env, parent, layout_ids, 1, result, element_layout);
result
}
_ => {
unreachable!(
"Invalid List layout for ListGetUnsafe operation: {:?}",
list_layout
);
}
}
}
/// List.append : List elem, elem -> List elem
pub fn list_append<'a, 'ctx, 'env>(
@ -346,7 +321,7 @@ pub fn list_set<'a, 'ctx, 'env>(
list: BasicValueEnum<'ctx>,
index: IntValue<'ctx>,
element: BasicValueEnum<'ctx>,
element_layout: &'a Layout<'a>,
element_layout: &Layout<'a>,
update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> {
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
@ -880,26 +855,20 @@ pub fn list_map4<'a, 'ctx, 'env>(
/// List.concat : List elem, List elem -> List elem
pub fn list_concat<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
_parent: FunctionValue<'ctx>,
first_list: BasicValueEnum<'ctx>,
second_list: BasicValueEnum<'ctx>,
list_layout: &Layout<'a>,
element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
match list_layout {
Layout::Builtin(Builtin::List(elem_layout)) => call_bitcode_fn_returns_list(
call_bitcode_fn_returns_list(
env,
&[
pass_list_cc(env, first_list),
pass_list_cc(env, second_list),
env.alignment_intvalue(elem_layout),
layout_width(env, elem_layout),
env.alignment_intvalue(element_layout),
layout_width(env, element_layout),
],
bitcode::LIST_CONCAT,
),
_ => {
unreachable!("Invalid List layout for List.concat {:?}", list_layout);
}
}
)
}
/// List.any : List elem, \(elem -> Bool) -> Bool

Binary file not shown.

View file

@ -1,12 +1,13 @@
use bumpalo::{self, collections::Vec};
use code_builder::Align;
use roc_builtins::bitcode::IntWidth;
use roc_collections::all::MutMap;
use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, Symbol};
use roc_mono::gen_refcount::{RefcountProcGenerator, REFCOUNT_MAX};
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
use roc_mono::layout::{Builtin, Layout, LayoutIds};
use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout};
use roc_reporting::internal_error;
use crate::layout::{CallConv, ReturnMethod, WasmLayout};
@ -58,6 +59,8 @@ pub struct WasmBackend<'a> {
/// how many blocks deep are we (used for jumps)
block_depth: u32,
joinpoint_label_map: MutMap<JoinPointId, (u32, Vec<'a, StoredValue>)>,
debug_current_proc_index: usize,
}
impl<'a> WasmBackend<'a> {
@ -149,6 +152,8 @@ impl<'a> WasmBackend<'a> {
code_builder: CodeBuilder::new(arena),
storage: Storage::new(arena),
symbol_layouts: MutMap::default(),
debug_current_proc_index: 0,
}
}
@ -203,6 +208,7 @@ impl<'a> WasmBackend<'a> {
pub fn build_proc(&mut self, proc: &Proc<'a>) {
// println!("\ngenerating procedure {:?}\n", proc.name);
self.debug_current_proc_index += 1;
self.start_proc(proc);
@ -293,18 +299,12 @@ impl<'a> WasmBackend<'a> {
self.build_expr(sym, expr, layout, &sym_storage);
// For primitives, we record that this symbol is at the top of the VM stack
// (For other values, we wrote to memory and there's nothing on the VM stack)
if let WasmLayout::Primitive(value_type, size) = wasm_layout {
let vm_state = self.code_builder.set_top_symbol(*sym);
self.storage.symbol_storage_map.insert(
*sym,
StoredValue::VirtualMachineStack {
vm_state,
value_type,
size,
},
);
// If this value is stored in the VM stack, we need code_builder to track it
// (since every instruction can change the VM stack)
if let Some(StoredValue::VirtualMachineStack { vm_state, .. }) =
self.storage.symbol_storage_map.get_mut(sym)
{
*vm_state = self.code_builder.set_top_symbol(*sym);
}
self.symbol_layouts.insert(*sym, *layout);
@ -529,7 +529,7 @@ impl<'a> WasmBackend<'a> {
}));
}
self.build_stmt(&rc_stmt, ret_layout);
self.build_stmt(rc_stmt, ret_layout);
}
x => todo!("statement {:?}", x),
@ -656,10 +656,9 @@ impl<'a> WasmBackend<'a> {
let (local_id, offset) =
location.local_and_offset(self.storage.stack_frame_pointer);
// This is a minor cheat. We only need the first two 32 bit
// chunks here. We fill both chunks with zeros, so we
// can simplify things to a single group of 64 bit operations instead of
// doing the below twice for 32 bits.
// This is a minor cheat.
// What we want to write to stack memory is { elements: null, length: 0 }
// But instead of two 32-bit stores, we can do a single 64-bit store.
self.code_builder.get_local(local_id);
self.code_builder.i64_const(0);
self.code_builder.i64_store(Align::Bytes4, offset);
@ -668,10 +667,281 @@ impl<'a> WasmBackend<'a> {
}
}
x => todo!("Expression {:?}", x),
Expr::Tag {
tag_layout: union_layout,
tag_id,
arguments,
..
} => self.build_tag(union_layout, *tag_id, arguments, *sym, storage),
Expr::GetTagId {
structure,
union_layout,
} => self.build_get_tag_id(*structure, union_layout),
Expr::UnionAtIndex {
structure,
tag_id,
union_layout,
index,
} => self.build_union_at_index(*structure, *tag_id, union_layout, *index, *sym),
_ => todo!("Expression `{}`", expr.to_pretty(100)),
}
}
fn build_tag(
&mut self,
union_layout: &UnionLayout<'a>,
tag_id: TagIdIntType,
arguments: &'a [Symbol],
symbol: Symbol,
stored: &StoredValue,
) {
if union_layout.tag_is_null(tag_id) {
self.code_builder.i32_const(0);
return;
}
let stores_tag_id_as_data = union_layout.stores_tag_id_as_data(PTR_SIZE);
let stores_tag_id_in_pointer = union_layout.stores_tag_id_in_pointer(PTR_SIZE);
let (data_size, data_alignment) = union_layout.data_size_and_alignment(PTR_SIZE);
// We're going to use the pointer many times, so put it in a local variable
let stored_with_local =
self.storage
.ensure_value_has_local(&mut self.code_builder, symbol, stored.to_owned());
let (local_id, data_offset) = match stored_with_local {
StoredValue::StackMemory { location, .. } => {
location.local_and_offset(self.storage.stack_frame_pointer)
}
StoredValue::Local { local_id, .. } => {
// Tag is stored as a pointer to the heap. Call the allocator to get a memory address.
self.allocate_with_refcount(Some(data_size), data_alignment, 1);
self.code_builder.set_local(local_id);
(local_id, 0)
}
StoredValue::VirtualMachineStack { .. } => {
internal_error!("{:?} should have a local variable", symbol)
}
};
// Write the field values to memory
let mut field_offset = data_offset;
for field_symbol in arguments.iter() {
field_offset += self.storage.copy_value_to_memory(
&mut self.code_builder,
local_id,
field_offset,
*field_symbol,
);
}
// Store the tag ID (if any)
if stores_tag_id_as_data {
let id_offset = data_offset + data_size - data_alignment;
let id_align = Align::from(data_alignment);
self.code_builder.get_local(local_id);
match id_align {
Align::Bytes1 => {
self.code_builder.i32_const(tag_id as i32);
self.code_builder.i32_store8(id_align, id_offset);
}
Align::Bytes2 => {
self.code_builder.i32_const(tag_id as i32);
self.code_builder.i32_store16(id_align, id_offset);
}
Align::Bytes4 => {
self.code_builder.i32_const(tag_id as i32);
self.code_builder.i32_store(id_align, id_offset);
}
Align::Bytes8 => {
self.code_builder.i64_const(tag_id as i64);
self.code_builder.i64_store(id_align, id_offset);
}
}
} else if stores_tag_id_in_pointer {
self.code_builder.get_local(local_id);
self.code_builder.i32_const(tag_id as i32);
self.code_builder.i32_or();
self.code_builder.set_local(local_id);
}
}
fn build_get_tag_id(&mut self, structure: Symbol, union_layout: &UnionLayout<'a>) {
use UnionLayout::*;
let mut need_to_close_block = false;
match union_layout {
NonRecursive(_) => {}
Recursive(_) => {}
NonNullableUnwrapped(_) => {
self.code_builder.i32_const(0);
return;
}
NullableWrapped { nullable_id, .. } => {
self.storage
.load_symbols(&mut self.code_builder, &[structure]);
self.code_builder.i32_eqz();
self.code_builder.if_(BlockType::Value(ValueType::I32));
self.code_builder.i32_const(*nullable_id as i32);
self.code_builder.else_();
need_to_close_block = true;
}
NullableUnwrapped { nullable_id, .. } => {
self.storage
.load_symbols(&mut self.code_builder, &[structure]);
self.code_builder.i32_eqz();
self.code_builder.if_(BlockType::Value(ValueType::I32));
self.code_builder.i32_const(*nullable_id as i32);
self.code_builder.else_();
self.code_builder.i32_const(!(*nullable_id) as i32);
self.code_builder.end();
}
};
if union_layout.stores_tag_id_as_data(PTR_SIZE) {
let (data_size, data_alignment) = union_layout.data_size_and_alignment(PTR_SIZE);
let id_offset = data_size - data_alignment;
let id_align = Align::from(data_alignment);
self.storage
.load_symbols(&mut self.code_builder, &[structure]);
match union_layout.tag_id_builtin() {
Builtin::Bool | Builtin::Int(IntWidth::U8) => {
self.code_builder.i32_load8_u(id_align, id_offset)
}
Builtin::Int(IntWidth::U16) => self.code_builder.i32_load16_u(id_align, id_offset),
Builtin::Int(IntWidth::U32) => self.code_builder.i32_load(id_align, id_offset),
Builtin::Int(IntWidth::U64) => self.code_builder.i64_load(id_align, id_offset),
x => internal_error!("Unexpected layout for tag union id {:?}", x),
}
} else if union_layout.stores_tag_id_in_pointer(PTR_SIZE) {
self.storage
.load_symbols(&mut self.code_builder, &[structure]);
self.code_builder.i32_const(3);
self.code_builder.i32_and();
}
if need_to_close_block {
self.code_builder.end();
}
}
fn build_union_at_index(
&mut self,
structure: Symbol,
tag_id: TagIdIntType,
union_layout: &UnionLayout<'a>,
index: u64,
symbol: Symbol,
) {
use UnionLayout::*;
debug_assert!(!union_layout.tag_is_null(tag_id));
let tag_index = tag_id as usize;
let field_layouts = match union_layout {
NonRecursive(tags) => tags[tag_index],
Recursive(tags) => tags[tag_index],
NonNullableUnwrapped(layouts) => *layouts,
NullableWrapped { other_tags, .. } => other_tags[tag_index],
NullableUnwrapped { other_fields, .. } => *other_fields,
};
let field_offset: u32 = field_layouts
.iter()
.take(index as usize)
.map(|field_layout| field_layout.stack_size(PTR_SIZE))
.sum();
// Get pointer and offset to the tag's data
let structure_storage = self.storage.get(&structure).to_owned();
let stored_with_local = self.storage.ensure_value_has_local(
&mut self.code_builder,
structure,
structure_storage,
);
let (tag_local_id, tag_offset) = match stored_with_local {
StoredValue::StackMemory { location, .. } => {
location.local_and_offset(self.storage.stack_frame_pointer)
}
StoredValue::Local { local_id, .. } => (local_id, 0),
StoredValue::VirtualMachineStack { .. } => {
internal_error!("{:?} should have a local variable", structure)
}
};
let stores_tag_id_in_pointer = union_layout.stores_tag_id_in_pointer(PTR_SIZE);
let from_ptr = if stores_tag_id_in_pointer {
let ptr = self.storage.create_anonymous_local(ValueType::I32);
self.code_builder.get_local(tag_local_id);
self.code_builder.i32_const(-4); // 11111111...1100
self.code_builder.i32_and();
self.code_builder.set_local(ptr);
ptr
} else {
tag_local_id
};
let from_offset = tag_offset + field_offset;
self.storage
.copy_value_from_memory(&mut self.code_builder, symbol, from_ptr, from_offset);
}
/// Allocate heap space and write an initial refcount
/// If the data size is known at compile time, pass it in comptime_data_size.
/// If size is only known at runtime, push *data* size to the VM stack first.
/// Leaves the *data* address on the VM stack
fn allocate_with_refcount(
&mut self,
comptime_data_size: Option<u32>,
alignment_bytes: u32,
initial_refcount: u32,
) {
// Add extra bytes for the refcount
let extra_bytes = alignment_bytes.max(PTR_SIZE);
if let Some(data_size) = comptime_data_size {
// Data size known at compile time and passed as an argument
self.code_builder
.i32_const((data_size + extra_bytes) as i32);
} else {
// Data size known only at runtime and is on top of VM stack
self.code_builder.i32_const(extra_bytes as i32);
self.code_builder.i32_add();
}
// Provide a constant for the alignment argument
self.code_builder.i32_const(alignment_bytes as i32);
// Call the foreign function. (Zig and C calling conventions are the same for this signature)
let param_types = bumpalo::vec![in self.env.arena; ValueType::I32, ValueType::I32];
let ret_type = Some(ValueType::I32);
self.call_zig_builtin("roc_alloc", param_types, ret_type);
// Save the allocation address to a temporary local variable
let local_id = self.storage.create_anonymous_local(ValueType::I32);
self.code_builder.set_local(local_id);
// Write the initial refcount
let refcount_offset = extra_bytes - PTR_SIZE;
let encoded_refcount = (initial_refcount as i32) - 1 + i32::MIN;
self.code_builder.get_local(local_id);
self.code_builder.i32_const(encoded_refcount);
self.code_builder.i32_store(Align::Bytes4, refcount_offset);
// Put the data address on the VM stack
self.code_builder.get_local(local_id);
self.code_builder.i32_const(extra_bytes as i32);
self.code_builder.i32_add();
}
fn build_low_level(
&mut self,
lowlevel: LowLevel,
@ -730,14 +1000,11 @@ impl<'a> WasmBackend<'a> {
};
}
StoredValue::StackMemory { location, .. } => match lit {
Literal::Decimal(decimal) => {
StoredValue::StackMemory { location, .. } => {
let mut write128 = |lower_bits, upper_bits| {
let (local_id, offset) =
location.local_and_offset(self.storage.stack_frame_pointer);
let lower_bits = decimal.0 as i64;
let upper_bits = (decimal.0 >> 64) as i64;
self.code_builder.get_local(local_id);
self.code_builder.i64_const(lower_bits);
self.code_builder.i64_store(Align::Bytes8, offset);
@ -745,6 +1012,22 @@ impl<'a> WasmBackend<'a> {
self.code_builder.get_local(local_id);
self.code_builder.i64_const(upper_bits);
self.code_builder.i64_store(Align::Bytes8, offset + 8);
};
match lit {
Literal::Decimal(decimal) => {
let lower_bits = (decimal.0 & 0xffff_ffff_ffff_ffff) as i64;
let upper_bits = (decimal.0 >> 64) as i64;
write128(lower_bits, upper_bits);
}
Literal::Int(x) => {
let lower_bits = (*x & 0xffff_ffff_ffff_ffff) as i64;
let upper_bits = (*x >> 64) as i64;
write128(lower_bits, upper_bits);
}
Literal::Float(_) => {
// Also not implemented in LLVM backend (nor in Rust!)
todo!("f128 type");
}
Literal::Str(string) => {
let (local_id, offset) =
@ -777,7 +1060,8 @@ impl<'a> WasmBackend<'a> {
};
}
_ => not_supported_error(),
},
}
}
_ => not_supported_error(),
};
@ -849,14 +1133,14 @@ impl<'a> WasmBackend<'a> {
// Not passing it as an argument because I'm trying to match Backend method signatures
let storage = self.storage.get(sym).to_owned();
if let Layout::Struct(field_layouts) = layout {
if matches!(layout, Layout::Struct(_)) {
match storage {
StoredValue::StackMemory { location, size, .. } => {
if size > 0 {
let (local_id, struct_offset) =
location.local_and_offset(self.storage.stack_frame_pointer);
let mut field_offset = struct_offset;
for (field, _) in fields.iter().zip(field_layouts.iter()) {
for field in fields.iter() {
field_offset += self.storage.copy_value_to_memory(
&mut self.code_builder,
local_id,
@ -934,4 +1218,18 @@ impl<'a> WasmBackend<'a> {
self.code_builder
.call(fn_index, linker_symbol_index, num_wasm_args, has_return_val);
}
/// Debug utility
///
/// if self._debug_current_proc_is("#UserApp_foo_1") {
/// self.code_builder._debug_assert_i32(0x1234);
/// }
fn _debug_current_proc_is(&self, linker_name: &'static str) -> bool {
let (_, linker_sym_index) = self.proc_symbols[self.debug_current_proc_index];
let sym_info = &self.linker_symbols[linker_sym_index as usize];
match sym_info {
SymInfo::Function(WasmObjectSymbol::Defined { name, .. }) => name == linker_name,
_ => false,
}
}
}

View file

@ -26,7 +26,7 @@ const PTR_TYPE: ValueType = ValueType::I32;
pub const STACK_POINTER_GLOBAL_ID: u32 = 0;
pub const FRAME_ALIGNMENT_BYTES: i32 = 16;
pub const MEMORY_NAME: &str = "memory";
pub const BUILTINS_IMPORT_MODULE_NAME: &str = "builtins";
pub const BUILTINS_IMPORT_MODULE_NAME: &str = "env";
pub const STACK_POINTER_NAME: &str = "__stack_pointer";
pub struct Env<'a> {
@ -176,21 +176,24 @@ pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) {
}
/// Round up to alignment_bytes (which must be a power of 2)
pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 {
if alignment_bytes <= 1 {
return unaligned;
}
if alignment_bytes.count_ones() != 1 {
internal_error!(
#[macro_export]
macro_rules! round_up_to_alignment {
($unaligned: expr, $alignment_bytes: expr) => {
if $alignment_bytes <= 1 {
$unaligned
} else if $alignment_bytes.count_ones() != 1 {
panic!(
"Cannot align to {} bytes. Not a power of 2.",
alignment_bytes
$alignment_bytes
);
}
let mut aligned = unaligned;
aligned += alignment_bytes - 1; // if lower bits are non-zero, push it over the next boundary
aligned &= -alignment_bytes; // mask with a flag that has upper bits 1, lower bits 0
} else {
let mut aligned = $unaligned;
aligned += $alignment_bytes - 1; // if lower bits are non-zero, push it over the next boundary
aligned &= !$alignment_bytes + 1; // mask with a flag that has upper bits 1, lower bits 0
aligned
}
};
}
pub fn debug_panic<E: std::fmt::Debug>(error: E) {
internal_error!("{:?}", error);

View file

@ -5,7 +5,7 @@ use roc_reporting::internal_error;
use crate::layout::{StackMemoryFormat::*, WasmLayout};
use crate::storage::{Storage, StoredValue};
use crate::wasm_module::{CodeBuilder, ValueType::*};
use crate::wasm_module::{Align, CodeBuilder, ValueType::*};
pub enum LowlevelBuildResult {
Done,
@ -17,7 +17,7 @@ pub fn decode_low_level<'a>(
code_builder: &mut CodeBuilder<'a>,
storage: &mut Storage<'a>,
lowlevel: LowLevel,
args: &'a [Symbol],
args: &[Symbol],
ret_layout: &WasmLayout,
) -> LowlevelBuildResult {
use LowlevelBuildResult::*;
@ -81,8 +81,6 @@ pub fn decode_low_level<'a>(
WasmLayout::Primitive(value_type, size) => match value_type {
I32 => {
code_builder.i32_add();
// TODO: is *deliberate* wrapping really in the spirit of things?
// The point of choosing NumAddWrap is to go fast by skipping checks, but we're making it slower.
wrap_i32(code_builder, *size);
}
I64 => code_builder.i64_add(),
@ -347,27 +345,57 @@ pub fn decode_low_level<'a>(
},
WasmLayout::StackMemory { .. } => return NotImplemented,
},
NumIsFinite => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
NumIsFinite => {
use StoredValue::*;
match storage.get(&args[0]) {
VirtualMachineStack { value_type, .. } | Local { value_type, .. } => {
match value_type {
I32 => code_builder.i32_const(1),
I64 => code_builder.i32_const(1),
F32 => {
code_builder.i32_reinterpret_f32();
code_builder.i32_const(0x7f800000);
code_builder.i32_const(0x7f80_0000);
code_builder.i32_and();
code_builder.i32_const(0x7f800000);
code_builder.i32_const(0x7f80_0000);
code_builder.i32_ne();
}
F64 => {
code_builder.i64_reinterpret_f64();
code_builder.i64_const(0x7ff0000000000000);
code_builder.i64_const(0x7ff0_0000_0000_0000);
code_builder.i64_and();
code_builder.i64_const(0x7ff0000000000000);
code_builder.i64_const(0x7ff0_0000_0000_0000);
code_builder.i64_ne();
}
},
WasmLayout::StackMemory { .. } => return NotImplemented,
},
}
}
StackMemory {
format, location, ..
} => {
let (local_id, offset) = location.local_and_offset(storage.stack_frame_pointer);
match format {
Int128 => code_builder.i32_const(1),
Float128 => {
code_builder.get_local(local_id);
code_builder.i64_load(Align::Bytes4, offset + 8);
code_builder.i64_const(0x7fff_0000_0000_0000);
code_builder.i64_and();
code_builder.i64_const(0x7fff_0000_0000_0000);
code_builder.i64_ne();
}
Decimal => {
code_builder.get_local(local_id);
code_builder.i64_load(Align::Bytes4, offset + 8);
code_builder.i64_const(0x7100_0000_0000_0000);
code_builder.i64_and();
code_builder.i64_const(0x7100_0000_0000_0000);
code_builder.i64_ne();
}
DataStructure => return NotImplemented,
}
}
}
}
NumAtan => {
let width = float_width_from_layout(ret_layout);
return BuiltinCall(&bitcode::NUM_ATAN[width]);
@ -468,16 +496,79 @@ pub fn decode_low_level<'a>(
WasmLayout::StackMemory { .. } => return NotImplemented,
}
}
Eq => match storage.get(&args[0]) {
StoredValue::VirtualMachineStack { value_type, .. }
| StoredValue::Local { value_type, .. } => match value_type {
Eq => {
use StoredValue::*;
match storage.get(&args[0]).to_owned() {
VirtualMachineStack { value_type, .. } | Local { value_type, .. } => {
match value_type {
I32 => code_builder.i32_eq(),
I64 => code_builder.i64_eq(),
F32 => code_builder.f32_eq(),
F64 => code_builder.f64_eq(),
},
StoredValue::StackMemory { .. } => return NotImplemented,
},
}
}
StackMemory {
format,
location: location0,
..
} => {
if let StackMemory {
location: location1,
..
} = storage.get(&args[1]).to_owned()
{
let stack_frame_pointer = storage.stack_frame_pointer;
let compare_bytes = |code_builder: &mut CodeBuilder| {
let (local0, offset0) = location0.local_and_offset(stack_frame_pointer);
let (local1, offset1) = location1.local_and_offset(stack_frame_pointer);
code_builder.get_local(local0);
code_builder.i64_load(Align::Bytes8, offset0);
code_builder.get_local(local1);
code_builder.i64_load(Align::Bytes8, offset1);
code_builder.i64_eq();
code_builder.get_local(local0);
code_builder.i64_load(Align::Bytes8, offset0 + 8);
code_builder.get_local(local1);
code_builder.i64_load(Align::Bytes8, offset1 + 8);
code_builder.i64_eq();
code_builder.i32_and();
};
match format {
Decimal => {
// Both args are finite
let first = [args[0]];
let second = [args[1]];
decode_low_level(
code_builder,
storage,
LowLevel::NumIsFinite,
&first,
ret_layout,
);
decode_low_level(
code_builder,
storage,
LowLevel::NumIsFinite,
&second,
ret_layout,
);
code_builder.i32_and();
// AND they have the same bytes
compare_bytes(code_builder);
code_builder.i32_and();
}
Int128 => compare_bytes(code_builder),
Float128 | DataStructure => return NotImplemented,
}
}
}
}
}
NotEq => match storage.get(&args[0]) {
StoredValue::VirtualMachineStack { value_type, .. }
| StoredValue::Local { value_type, .. } => match value_type {
@ -486,7 +577,10 @@ pub fn decode_low_level<'a>(
F32 => code_builder.f32_ne(),
F64 => code_builder.f64_ne(),
},
StoredValue::StackMemory { .. } => return NotImplemented,
StoredValue::StackMemory { .. } => {
decode_low_level(code_builder, storage, LowLevel::Eq, args, ret_layout);
code_builder.i32_eqz();
}
},
And => code_builder.i32_and(),
Or => code_builder.i32_or(),

View file

@ -108,11 +108,17 @@ impl<'a> Storage<'a> {
self.stack_frame_size = 0;
}
/// Internal use only. If you think you want it externally, you really want `allocate`
/// Internal use only. See `allocate` or `create_anonymous_local`
fn get_next_local_id(&self) -> LocalId {
LocalId((self.arg_types.len() + self.local_types.len()) as u32)
}
pub fn create_anonymous_local(&mut self, value_type: ValueType) -> LocalId {
let id = self.get_next_local_id();
self.local_types.push(value_type);
id
}
/// Allocate storage for a Roc value
///
/// Wasm primitives (i32, i64, f32, f64) are allocated "storage" on the VM stack.
@ -172,7 +178,7 @@ impl<'a> Storage<'a> {
}
let offset =
round_up_to_alignment(self.stack_frame_size, *alignment_bytes as i32);
round_up_to_alignment!(self.stack_frame_size, *alignment_bytes as i32);
self.stack_frame_size = offset + (*size as i32);

View file

@ -85,7 +85,9 @@ impl std::fmt::Debug for VmBlock<'_> {
}
}
/// Wasm memory alignment. (Rust representation matches Wasm encoding)
/// Wasm memory alignment for load/store instructions.
/// Rust representation matches Wasm encoding.
/// It's an error to specify alignment higher than the "natural" alignment of the instruction
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum Align {
@ -93,10 +95,6 @@ pub enum Align {
Bytes2 = 1,
Bytes4 = 2,
Bytes8 = 3,
Bytes16 = 4,
Bytes32 = 5,
Bytes64 = 6,
// ... we can add more if we need them ...
}
impl From<u32> for Align {
@ -105,11 +103,13 @@ impl From<u32> for Align {
1 => Align::Bytes1,
2 => Align::Bytes2,
4 => Align::Bytes4,
8 => Align::Bytes8,
16 => Align::Bytes16,
32 => Align::Bytes32,
64 => Align::Bytes64,
_ => internal_error!("{:?}-byte alignment not supported", x),
_ => {
if x.count_ones() == 1 {
Align::Bytes8 // Max value supported by any Wasm instruction
} else {
internal_error!("Cannot align to {} bytes", x);
}
}
}
}
}
@ -445,7 +445,7 @@ impl<'a> CodeBuilder<'a> {
if frame_size != 0 {
if let Some(frame_ptr_id) = frame_pointer {
let aligned_size = round_up_to_alignment(frame_size, FRAME_ALIGNMENT_BYTES);
let aligned_size = round_up_to_alignment!(frame_size, FRAME_ALIGNMENT_BYTES);
self.build_stack_frame_push(aligned_size, frame_ptr_id);
self.build_stack_frame_pop(aligned_size, frame_ptr_id);
}
@ -901,4 +901,17 @@ impl<'a> CodeBuilder<'a> {
instruction_no_args!(i64_reinterpret_f64, I64REINTERPRETF64, 1, true);
instruction_no_args!(f32_reinterpret_i32, F32REINTERPRETI32, 1, true);
instruction_no_args!(f64_reinterpret_i64, F64REINTERPRETI64, 1, true);
/// Generate a debug assertion for an expected i32 value
pub fn _debug_assert_i32(&mut self, expected: i32) {
self.i32_const(expected);
self.i32_eq();
self.i32_eqz();
self.if_(BlockType::NoResult);
self.unreachable_(); // Tell Wasm runtime to throw an exception
self.end();
// It matches. Restore the original value to the VM stack and continue the program.
// We know it matched the expected value, so just use that!
self.i32_const(expected);
}
}

View file

@ -23,9 +23,9 @@ use roc_mono::ir::{
UpdateModeIds,
};
use roc_mono::layout::{Layout, LayoutCache, LayoutProblem};
use roc_parse::ast::{self, StrLiteral, TypeAnnotation};
use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation};
use roc_parse::header::{
ExposesEntry, ImportsEntry, PackageEntry, PackageOrPath, PlatformHeader, To, TypedIdent,
ExposedName, ImportsEntry, PackageEntry, PackageOrPath, PlatformHeader, To, TypedIdent,
};
use roc_parse::module::module_defs;
use roc_parse::parser::{self, ParseProblem, Parser, SyntaxError};
@ -684,7 +684,7 @@ enum HeaderFor<'a> {
to_platform: To<'a>,
},
PkgConfig {
/// usually `base`
/// usually `pf`
config_shorthand: &'a str,
/// the type scheme of the main function (required by the platform)
/// (currently unused)
@ -2553,8 +2553,8 @@ fn parse_header<'a>(
opt_shorthand,
header_src,
packages: &[],
exposes: header.exposes.items,
imports: header.imports.items,
exposes: unspace(arena, header.exposes.items),
imports: unspace(arena, header.imports.items),
to_platform: None,
};
@ -2575,7 +2575,7 @@ fn parse_header<'a>(
std::str::from_utf8_unchecked(&src_bytes[..chomped])
};
let packages = header.packages.items;
let packages = unspace(arena, header.packages.items);
let info = HeaderInfo {
loc_name: Located {
@ -2587,8 +2587,8 @@ fn parse_header<'a>(
opt_shorthand,
header_src,
packages,
exposes: header.provides.items,
imports: header.imports.items,
exposes: unspace(arena, header.provides.items),
imports: unspace(arena, header.imports.items),
to_platform: Some(header.to.value),
};
@ -2602,19 +2602,17 @@ fn parse_header<'a>(
match header.to.value {
To::ExistingPackage(existing_package) => {
let opt_base_package = packages.iter().find(|loc_package_entry| {
let opt_base_package = packages.iter().find_map(|loc_package_entry| {
let Located { value, .. } = loc_package_entry;
match value {
PackageEntry::Entry { shorthand, .. } => shorthand == &existing_package,
_ => false,
if value.shorthand == existing_package {
Some(value)
} else {
None
}
});
match opt_base_package {
Some(Located {
value:
PackageEntry::Entry {
if let Some(PackageEntry {
shorthand,
package_or_path:
Located {
@ -2622,9 +2620,8 @@ fn parse_header<'a>(
..
},
..
},
..
}) => {
}) = opt_base_package
{
match package_or_path {
PackageOrPath::Path(StrLiteral::PlainLine(package)) => {
// check whether we can find a Package-Config.roc file
@ -2645,10 +2642,7 @@ 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))
@ -2656,8 +2650,8 @@ fn parse_header<'a>(
}
_ => unreachable!(),
}
}
_ => panic!("could not find base"),
} else {
panic!("could not find base")
}
}
To::NewPackage(package_or_path) => match package_or_path {
@ -2765,7 +2759,7 @@ struct HeaderInfo<'a> {
opt_shorthand: Option<&'a str>,
header_src: &'a str,
packages: &'a [Located<PackageEntry<'a>>],
exposes: &'a [Located<ExposesEntry<'a, &'a str>>],
exposes: &'a [Located<ExposedName<'a>>],
imports: &'a [Located<ImportsEntry<'a>>],
to_platform: Option<To<'a>>,
}
@ -2845,7 +2839,7 @@ fn send_header<'a>(
// For each of our imports, add an entry to deps_by_name
//
// e.g. for `imports [ base.Foo.{ bar } ]`, add `Foo` to deps_by_name
// e.g. for `imports [ pf.Foo.{ bar } ]`, add `Foo` to deps_by_name
//
// Also build a list of imported_values_to_expose (like `bar` above.)
for (qualified_module_name, exposed_idents, region) in imported.into_iter() {
@ -2912,24 +2906,13 @@ 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);
}
SpaceBefore(inner, _) | SpaceAfter(inner, _) => {
parse_entries.push(inner);
}
}
}
let package_entries = packages
.iter()
.map(|pkg| {
let pkg = pkg.value;
(pkg.shorthand, pkg.package_or_path.value)
})
.collect::<MutMap<_, _>>();
// Send the deps to the coordinator thread for processing,
// then continue on to parsing and canonicalizing defs.
@ -2988,7 +2971,7 @@ struct PlatformHeaderInfo<'a> {
header_src: &'a str,
app_module_id: ModuleId,
packages: &'a [Located<PackageEntry<'a>>],
provides: &'a [Located<ExposesEntry<'a, &'a str>>],
provides: &'a [Located<ExposedName<'a>>],
requires: &'a [Located<TypedIdent<'a>>],
imports: &'a [Located<ImportsEntry<'a>>],
}
@ -2996,7 +2979,6 @@ struct PlatformHeaderInfo<'a> {
// TODO refactor so more logic is shared with `send_header`
#[allow(clippy::too_many_arguments)]
fn send_header_two<'a>(
arena: &'a Bump,
info: PlatformHeaderInfo<'a>,
parse_state: parser::State<'a>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
@ -3066,7 +3048,7 @@ fn send_header_two<'a>(
// For each of our imports, add an entry to deps_by_name
//
// e.g. for `imports [ base.Foo.{ bar } ]`, add `Foo` to deps_by_name
// e.g. for `imports [ pf.Foo.{ bar } ]`, add `Foo` to deps_by_name
//
// Also build a list of imported_values_to_expose (like `bar` above.)
for (qualified_module_name, exposed_idents, region) in imported.into_iter() {
@ -3104,15 +3086,17 @@ fn send_header_two<'a>(
.entry(app_module_id)
.or_insert_with(IdentIds::default);
for (loc_ident, _) in unpack_exposes_entries(arena, requires) {
let ident: Ident = loc_ident.value.into();
for entry in requires {
let entry = entry.value;
let ident: Ident = entry.ident.value.into();
let ident_id = ident_ids.get_or_insert(&ident);
let symbol = Symbol::new(app_module_id, ident_id);
// Since this value is exposed, add it to our module's default scope.
debug_assert!(!scope.contains_key(&ident.clone()));
scope.insert(ident, (symbol, loc_ident.region));
scope.insert(ident, (symbol, entry.ident.region));
}
}
@ -3145,24 +3129,10 @@ fn send_header_two<'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);
}
SpaceBefore(inner, _) | SpaceAfter(inner, _) => {
parse_entries.push(inner);
}
}
}
let package_entries = packages
.iter()
.map(|pkg| (pkg.value.shorthand, pkg.value.package_or_path.value))
.collect::<MutMap<_, _>>();
// Send the deps to the coordinator thread for processing,
// then continue on to parsing and canonicalizing defs.
@ -3341,6 +3311,16 @@ fn run_solve<'a>(
}
}
fn unspace<'a, T: Copy>(arena: &'a Bump, items: &[Located<Spaced<'a, T>>]) -> &'a [Located<T>] {
bumpalo::collections::Vec::from_iter_in(
items
.iter()
.map(|item| Located::at(item.region, item.value.extract_spaces().item)),
arena,
)
.into_bump_slice()
}
#[allow(clippy::too_many_arguments)]
fn fabricate_pkg_config_module<'a>(
arena: &'a Bump,
@ -3354,8 +3334,6 @@ fn fabricate_pkg_config_module<'a>(
header_src: &'a str,
module_timing: ModuleTiming,
) -> (ModuleId, Msg<'a>) {
let provides: &'a [Located<ExposesEntry<'a, &'a str>>] = header.provides.items;
let info = PlatformHeaderInfo {
filename,
is_root_module: false,
@ -3363,13 +3341,15 @@ fn fabricate_pkg_config_module<'a>(
header_src,
app_module_id,
packages: &[],
provides,
requires: arena.alloc([header.requires.signature]),
imports: header.imports.items,
provides: unspace(arena, header.provides.items),
requires: &*arena.alloc([Located::at(
header.requires.signature.region,
header.requires.signature.extract_spaces().item,
)]),
imports: unspace(arena, header.imports.items),
};
send_header_two(
arena,
info,
parse_state,
module_ids,
@ -3412,14 +3392,14 @@ fn fabricate_effects_module<'a>(
let mut module_ids = (*module_ids).lock();
for exposed in header.exposes.iter() {
if let ExposesEntry::Exposed(module_name) = exposed.value {
let module_name = exposed.value.extract_spaces().item;
module_ids.get_or_insert(&PQModuleName::Qualified(
shorthand,
module_name.as_str().into(),
));
}
}
}
let exposed_ident_ids = {
// Lock just long enough to perform the minimal operations necessary.
@ -3632,33 +3612,16 @@ fn fabricate_effects_module<'a>(
fn unpack_exposes_entries<'a>(
arena: &'a Bump,
entries: &'a [Located<TypedIdent<'a>>],
) -> bumpalo::collections::Vec<'a, (&'a Located<&'a str>, &'a Located<TypeAnnotation<'a>>)> {
entries: &'a [Located<Spaced<'a, TypedIdent<'a>>>],
) -> bumpalo::collections::Vec<'a, (Located<&'a str>, Located<TypeAnnotation<'a>>)> {
use bumpalo::collections::Vec;
let mut stack: Vec<&TypedIdent> = Vec::with_capacity_in(entries.len(), arena);
let mut output = Vec::with_capacity_in(entries.len(), arena);
let iter = entries.iter().map(|entry| {
let entry: TypedIdent<'a> = entry.value.extract_spaces().item;
(entry.ident, entry.ann)
});
for entry in entries.iter() {
stack.push(&entry.value);
}
while let Some(effects_entry) = stack.pop() {
match effects_entry {
TypedIdent::Entry {
ident,
spaces_before_colon: _,
ann,
} => {
output.push((ident, ann));
}
TypedIdent::SpaceAfter(nested, _) | TypedIdent::SpaceBefore(nested, _) => {
stack.push(nested);
}
}
}
output
Vec::from_iter_in(iter, arena)
}
#[allow(clippy::too_many_arguments)]
@ -3848,21 +3811,11 @@ fn exposed_from_import<'a>(entry: &ImportsEntry<'a>) -> (QualifiedModuleName<'a>
(qualified_module_name, exposed)
}
SpaceBefore(sub_entry, _) | SpaceAfter(sub_entry, _) => {
// Ignore spaces.
exposed_from_import(*sub_entry)
}
}
}
fn ident_from_exposed(entry: &ExposesEntry<'_, &str>) -> Ident {
use roc_parse::header::ExposesEntry::*;
match entry {
Exposed(ident) => (*ident).into(),
SpaceBefore(sub_entry, _) | SpaceAfter(sub_entry, _) => ident_from_exposed(sub_entry),
}
fn ident_from_exposed(entry: &Spaced<'_, ExposedName<'_>>) -> Ident {
entry.extract_spaces().item.as_str().into()
}
#[allow(clippy::too_many_arguments)]
@ -4428,7 +4381,7 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin
alloc.reflow("I could not find a platform based on your input file."),
alloc.reflow(r"Does the module header contain an entry that looks like this:"),
alloc
.parser_suggestion(" packages { base: \"platform\" }")
.parser_suggestion(" packages { pf: \"platform\" }")
.indent(4),
alloc.reflow("See also TODO."),
]);

View file

@ -359,7 +359,7 @@ impl fmt::Debug for ModuleId {
}
}
/// base.Task
/// pf.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

View file

@ -77,7 +77,17 @@ impl<'a> RefcountProcGenerator<'a> {
layout: Layout<'a>,
modify: &ModifyRc,
following: &'a Stmt<'a>,
) -> (Stmt<'a>, Option<(Symbol, ProcLayout<'a>)>) {
) -> (&'a Stmt<'a>, Option<(Symbol, ProcLayout<'a>)>) {
if !Self::layout_is_supported(&layout) {
// Just a warning, so we can decouple backend development from refcounting development.
// When we are closer to completion, we can change it to a panic.
println!(
"WARNING! MEMORY LEAK! Refcounting not yet implemented for Layout {:?}",
layout
);
return (following, None);
}
let arena = self.arena;
match modify {
@ -105,7 +115,7 @@ impl<'a> RefcountProcGenerator<'a> {
arguments: arena.alloc([*structure, amount_sym]),
});
let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following);
let rc_stmt = amount_stmt(arena.alloc(call_stmt));
let rc_stmt = arena.alloc(amount_stmt(arena.alloc(call_stmt)));
// Create a linker symbol for the helper proc if this is the first usage
let new_proc_info = if is_existing {
@ -140,7 +150,12 @@ impl<'a> RefcountProcGenerator<'a> {
arguments: arena.alloc([*structure]),
});
let rc_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following);
let rc_stmt = arena.alloc(Stmt::Let(
call_result_empty,
call_expr,
LAYOUT_UNIT,
following,
));
// Create a linker symbol for the helper proc if this is the first usage
let new_proc_info = if is_existing {
@ -182,13 +197,19 @@ impl<'a> RefcountProcGenerator<'a> {
arguments: arena.alloc([rc_ptr_sym]),
});
let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following);
let rc_stmt = rc_ptr_stmt(arena.alloc(call_stmt));
let rc_stmt = arena.alloc(rc_ptr_stmt(arena.alloc(call_stmt)));
(rc_stmt, None)
}
}
}
// TODO: consider refactoring so that we have just one place to define what's supported
// (Probably by generating procs on the fly instead of all at the end)
fn layout_is_supported(layout: &Layout) -> bool {
matches!(layout, Layout::Builtin(Builtin::Str))
}
/// Generate refcounting helper procs, each specialized to a particular Layout.
/// For example `List (Result { a: Str, b: Int } Str)` would get its own helper
/// to update the refcounts on the List, the Result and the strings.
@ -197,20 +218,24 @@ impl<'a> RefcountProcGenerator<'a> {
arena: &'a Bump,
ident_ids: &mut IdentIds,
) -> Vec<'a, Proc<'a>> {
// Move the vector so we can loop over it safely
let mut procs_to_generate = Vec::with_capacity_in(0, arena);
std::mem::swap(&mut self.procs_to_generate, &mut procs_to_generate);
// Move the vector out of self, so we can loop over it safely
let mut procs_to_generate =
std::mem::replace(&mut self.procs_to_generate, Vec::with_capacity_in(0, arena));
let mut procs = Vec::with_capacity_in(procs_to_generate.len(), arena);
for (layout, op, proc_symbol) in procs_to_generate.drain(0..) {
let proc = match layout {
Layout::Builtin(Builtin::Str) => self.gen_modify_str(ident_ids, op, proc_symbol),
_ => todo!("Refcounting is not yet implemented for Layout {:?}", layout),
};
procs.push(proc);
let procs_iter = procs_to_generate
.drain(0..)
.map(|(layout, op, proc_symbol)| {
debug_assert!(Self::layout_is_supported(&layout));
match layout {
Layout::Builtin(Builtin::Str) => {
self.gen_modify_str(ident_ids, op, proc_symbol)
}
procs
_ => todo!("Please update layout_is_supported for {:?}", layout),
}
});
Vec::from_iter_in(procs_iter, arena)
}
/// Find the Symbol of the procedure for this layout and refcount operation,

View file

@ -207,20 +207,24 @@ pub enum UnionLayout<'a> {
/// A non-recursive tag union
/// e.g. `Result a e : [ Ok a, Err e ]`
NonRecursive(&'a [&'a [Layout<'a>]]),
/// A recursive tag union
/// A recursive tag union (general case)
/// e.g. `Expr : [ Sym Str, Add Expr Expr ]`
Recursive(&'a [&'a [Layout<'a>]]),
/// A recursive tag union with just one constructor
/// Optimization: No need to store a tag ID (the payload is "unwrapped")
/// e.g. `RoseTree a : [ Tree a (List (RoseTree a)) ]`
NonNullableUnwrapped(&'a [Layout<'a>]),
/// A recursive tag union where the non-nullable variant(s) store the tag id
/// A recursive tag union that has an empty variant
/// Optimization: Represent the empty variant as null pointer => no memory usage & fast comparison
/// It has more than one other variant, so they need tag IDs (payloads are "wrapped")
/// e.g. `FingerTree a : [ Empty, Single a, More (Some a) (FingerTree (Tuple a)) (Some a) ]`
/// see also: https://youtu.be/ip92VMpf_-A?t=164
NullableWrapped {
nullable_id: u16,
other_tags: &'a [&'a [Layout<'a>]],
},
/// A recursive tag union where the non-nullable variant does NOT store the tag id
/// A recursive tag union with only two variants, where one is empty.
/// Optimizations: Use null for the empty variant AND don't store a tag ID for the other variant.
/// e.g. `ConsList a : [ Nil, Cons a (ConsList a) ]`
NullableUnwrapped {
nullable_id: bool,
@ -422,6 +426,58 @@ impl<'a> UnionLayout<'a> {
// because we store a refcount, the alignment must be at least the size of a pointer
allocation.max(pointer_size)
}
/// Size of the data in memory, whether it's stack or heap (for non-null tag ids)
pub fn data_size_and_alignment(&self, pointer_size: u32) -> (u32, u32) {
let id_data_layout = if self.stores_tag_id_as_data(pointer_size) {
Some(self.tag_id_layout())
} else {
None
};
match self {
Self::NonRecursive(tags) => {
Self::data_size_and_alignment_help(tags, id_data_layout, pointer_size)
}
Self::Recursive(tags) => {
Self::data_size_and_alignment_help(tags, id_data_layout, pointer_size)
}
Self::NonNullableUnwrapped(fields) => {
Self::data_size_and_alignment_help(&[fields], id_data_layout, pointer_size)
}
Self::NullableWrapped { other_tags, .. } => {
Self::data_size_and_alignment_help(other_tags, id_data_layout, pointer_size)
}
Self::NullableUnwrapped { other_fields, .. } => {
Self::data_size_and_alignment_help(&[other_fields], id_data_layout, pointer_size)
}
}
}
fn data_size_and_alignment_help(
variant_field_layouts: &[&[Layout]],
id_data_layout: Option<Layout>,
pointer_size: u32,
) -> (u32, u32) {
let mut size = 0;
let mut alignment_bytes = 0;
for field_layouts in variant_field_layouts {
let mut data = Layout::Struct(field_layouts);
let fields_and_id;
if let Some(id_layout) = id_data_layout {
fields_and_id = [data, id_layout];
data = Layout::Struct(&fields_and_id);
}
let (variant_size, variant_alignment) = data.stack_size_and_alignment(pointer_size);
alignment_bytes = alignment_bytes.max(variant_alignment);
size = size.max(variant_size);
}
(size, alignment_bytes)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
@ -842,6 +898,14 @@ impl<'a> Layout<'a> {
round_up_to_alignment(width, alignment)
}
pub fn stack_size_and_alignment(&self, pointer_size: u32) -> (u32, u32) {
let width = self.stack_size_without_alignment(pointer_size);
let alignment = self.alignment_bytes(pointer_size);
let size = round_up_to_alignment(width, alignment);
(size, alignment)
}
fn stack_size_without_alignment(&self, pointer_size: u32) -> u32 {
use Layout::*;
@ -956,14 +1020,9 @@ impl<'a> Layout<'a> {
use Layout::*;
match self {
Union(variant) => {
use UnionLayout::*;
Union(UnionLayout::NonRecursive(_)) => false,
matches!(
variant,
Recursive(_) | NullableWrapped { .. } | NullableUnwrapped { .. }
)
}
Union(_) => true,
RecursivePointer => true,

View file

@ -1,9 +1,6 @@
use std::fmt::Debug;
use crate::header::{
AppHeader, ExposesEntry, ImportsEntry, InterfaceHeader, PackageEntry, PlatformHeader,
PlatformRigid, TypedIdent,
};
use crate::header::{AppHeader, InterfaceHeader, PlatformHeader};
use crate::ident::Ident;
use bumpalo::collections::{String, Vec};
use bumpalo::Bump;
@ -17,6 +14,33 @@ pub struct Spaces<'a, T> {
pub after: &'a [CommentOrNewline<'a>],
}
#[derive(Copy, Clone, PartialEq)]
pub enum Spaced<'a, T> {
Item(T),
// Spaces
SpaceBefore(&'a Spaced<'a, T>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a Spaced<'a, T>, &'a [CommentOrNewline<'a>]),
}
impl<'a, T: Debug> Debug for Spaced<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Item(item) => item.fmt(f),
Self::SpaceBefore(item, space) => f
.debug_tuple("SpaceBefore")
.field(item)
.field(space)
.finish(),
Self::SpaceAfter(item, space) => f
.debug_tuple("SpaceAfter")
.field(item)
.field(space)
.finish(),
}
}
}
pub trait ExtractSpaces<'a>: Sized + Copy {
type Item;
fn extract_spaces(&self) -> Spaces<'a, Self::Item>;
@ -674,6 +698,15 @@ pub trait Spaceable<'a> {
}
}
impl<'a, T> Spaceable<'a> for Spaced<'a, T> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
Spaced::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
Spaced::SpaceAfter(self, spaces)
}
}
impl<'a> Spaceable<'a> for Expr<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
Expr::SpaceBefore(self, spaces)
@ -701,24 +734,6 @@ impl<'a> Spaceable<'a> for TypeAnnotation<'a> {
}
}
impl<'a> Spaceable<'a> for ImportsEntry<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
ImportsEntry::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
ImportsEntry::SpaceAfter(self, spaces)
}
}
impl<'a> Spaceable<'a> for TypedIdent<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
TypedIdent::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
TypedIdent::SpaceAfter(self, spaces)
}
}
impl<'a, Val> Spaceable<'a> for AssignedField<'a, Val> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
AssignedField::SpaceBefore(self, spaces)
@ -823,8 +838,55 @@ macro_rules! impl_extract_spaces {
impl_extract_spaces!(Expr);
impl_extract_spaces!(Tag);
impl_extract_spaces!(AssignedField<T>);
impl_extract_spaces!(PlatformRigid);
impl_extract_spaces!(TypedIdent);
impl_extract_spaces!(ImportsEntry);
impl_extract_spaces!(ExposesEntry<T>);
impl_extract_spaces!(PackageEntry);
impl<'a, T: Copy> ExtractSpaces<'a> for Spaced<'a, T> {
type Item = T;
fn extract_spaces(&self) -> Spaces<'a, T> {
match self {
Spaced::SpaceBefore(item, before) => match item {
Spaced::SpaceBefore(_, _) => todo!(),
Spaced::SpaceAfter(item, after) => {
if let Spaced::Item(item) = item {
Spaces {
before,
item: *item,
after,
}
} else {
todo!();
}
}
Spaced::Item(item) => Spaces {
before,
item: *item,
after: &[],
},
},
Spaced::SpaceAfter(item, after) => match item {
Spaced::SpaceBefore(item, before) => {
if let Spaced::Item(item) = item {
Spaces {
before,
item: *item,
after,
}
} else {
todo!();
}
}
Spaced::SpaceAfter(_, _) => todo!(),
Spaced::Item(item) => Spaces {
before: &[],
item: *item,
after,
},
},
Spaced::Item(item) => Spaces {
before: &[],
item: *item,
after: &[],
},
}
}
}

View file

@ -1,4 +1,4 @@
use crate::ast::{Collection, CommentOrNewline, Spaceable, StrLiteral, TypeAnnotation};
use crate::ast::{Collection, CommentOrNewline, Spaced, StrLiteral, TypeAnnotation};
use crate::blankspace::space0_e;
use crate::ident::lowercase_ident;
use crate::parser::Progress::{self, *};
@ -57,11 +57,30 @@ impl<'a> ModuleName<'a> {
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub struct ExposedName<'a>(&'a str);
impl<'a> From<ExposedName<'a>> for &'a str {
fn from(name: ExposedName<'a>) -> Self {
name.0
}
}
impl<'a> ExposedName<'a> {
pub fn new(name: &'a str) -> Self {
ExposedName(name)
}
pub fn as_str(&'a self) -> &'a str {
self.0
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct InterfaceHeader<'a> {
pub name: Loc<ModuleName<'a>>,
pub exposes: Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub imports: Collection<'a, Loc<ImportsEntry<'a>>>,
pub exposes: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
pub imports: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
// Potential comments and newlines - these will typically all be empty.
pub before_header: &'a [CommentOrNewline<'a>],
@ -81,9 +100,9 @@ pub enum To<'a> {
#[derive(Clone, Debug, PartialEq)]
pub struct AppHeader<'a> {
pub name: Loc<StrLiteral<'a>>,
pub packages: Collection<'a, Loc<PackageEntry<'a>>>,
pub imports: Collection<'a, Loc<ImportsEntry<'a>>>,
pub provides: Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub packages: Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>,
pub imports: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
pub provides: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
pub to: Loc<To<'a>>,
// Potential comments and newlines - these will typically all be empty.
@ -102,7 +121,7 @@ pub struct AppHeader<'a> {
#[derive(Clone, Debug, PartialEq)]
pub struct PackageHeader<'a> {
pub name: Loc<PackageName<'a>>,
pub exposes: Vec<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub exposes: Vec<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
pub packages: Vec<'a, (Loc<&'a str>, Loc<PackageOrPath<'a>>)>,
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
@ -118,37 +137,25 @@ pub struct PackageHeader<'a> {
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum PlatformRigid<'a> {
Entry { rigid: &'a str, alias: &'a str },
// Spaces
SpaceBefore(&'a PlatformRigid<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a PlatformRigid<'a>, &'a [CommentOrNewline<'a>]),
}
impl<'a> Spaceable<'a> for PlatformRigid<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
PlatformRigid::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
PlatformRigid::SpaceAfter(self, spaces)
}
pub struct PlatformRigid<'a> {
pub rigid: &'a str,
pub alias: &'a str,
}
#[derive(Clone, Debug, PartialEq)]
pub struct PlatformRequires<'a> {
pub rigids: Collection<'a, Loc<PlatformRigid<'a>>>,
pub signature: Loc<TypedIdent<'a>>,
pub rigids: Collection<'a, Loc<Spaced<'a, PlatformRigid<'a>>>>,
pub signature: Loc<Spaced<'a, TypedIdent<'a>>>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct PlatformHeader<'a> {
pub name: Loc<PackageName<'a>>,
pub requires: PlatformRequires<'a>,
pub exposes: Collection<'a, Loc<ExposesEntry<'a, ModuleName<'a>>>>,
pub packages: Collection<'a, Loc<PackageEntry<'a>>>,
pub imports: Collection<'a, Loc<ImportsEntry<'a>>>,
pub provides: Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub exposes: Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>,
pub packages: Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>,
pub imports: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
pub provides: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
pub effects: Effects<'a>,
// Potential comments and newlines - these will typically all be empty.
@ -174,26 +181,7 @@ pub struct Effects<'a> {
pub spaces_after_type_name: &'a [CommentOrNewline<'a>],
pub effect_shortname: &'a str,
pub effect_type_name: &'a str,
pub entries: Collection<'a, Loc<TypedIdent<'a>>>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ExposesEntry<'a, T> {
/// e.g. `Task`
Exposed(T),
// Spaces
SpaceBefore(&'a ExposesEntry<'a, T>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a ExposesEntry<'a, T>, &'a [CommentOrNewline<'a>]),
}
impl<'a, T> Spaceable<'a> for ExposesEntry<'a, T> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
ExposesEntry::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
ExposesEntry::SpaceAfter(self, spaces)
}
pub entries: Collection<'a, Loc<Spaced<'a, TypedIdent<'a>>>>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
@ -201,71 +189,35 @@ pub enum ImportsEntry<'a> {
/// e.g. `Task` or `Task.{ Task, after }`
Module(
ModuleName<'a>,
Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
),
/// e.g. `base.Task` or `base.Task.{ after }` or `base.{ Task.{ Task, after } }`
/// e.g. `pf.Task` or `pf.Task.{ after }` or `pf.{ Task.{ Task, after } }`
Package(
&'a str,
ModuleName<'a>,
Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
),
// Spaces
SpaceBefore(&'a ImportsEntry<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a ImportsEntry<'a>, &'a [CommentOrNewline<'a>]),
}
impl<'a> ExposesEntry<'a, &'a str> {
pub fn as_str(&'a self) -> &'a str {
use ExposesEntry::*;
match self {
Exposed(string) => string,
SpaceBefore(sub_entry, _) | SpaceAfter(sub_entry, _) => sub_entry.as_str(),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum TypedIdent<'a> {
/// e.g.
///
/// printLine : Str -> Effect {}
Entry {
ident: Loc<&'a str>,
spaces_before_colon: &'a [CommentOrNewline<'a>],
ann: Loc<TypeAnnotation<'a>>,
},
// Spaces
SpaceBefore(&'a TypedIdent<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a TypedIdent<'a>, &'a [CommentOrNewline<'a>]),
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct TypedIdent<'a> {
pub ident: Loc<&'a str>,
pub spaces_before_colon: &'a [CommentOrNewline<'a>],
pub ann: Loc<TypeAnnotation<'a>>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum PackageEntry<'a> {
Entry {
shorthand: &'a str,
spaces_after_shorthand: &'a [CommentOrNewline<'a>],
package_or_path: Loc<PackageOrPath<'a>>,
},
// Spaces
SpaceBefore(&'a PackageEntry<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a PackageEntry<'a>, &'a [CommentOrNewline<'a>]),
pub struct PackageEntry<'a> {
pub shorthand: &'a str,
pub spaces_after_shorthand: &'a [CommentOrNewline<'a>],
pub package_or_path: Loc<PackageOrPath<'a>>,
}
impl<'a> Spaceable<'a> for PackageEntry<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
PackageEntry::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
PackageEntry::SpaceAfter(self, spaces)
}
}
pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>, EPackageEntry<'a>> {
pub fn package_entry<'a>() -> impl Parser<'a, Spaced<'a, PackageEntry<'a>>, EPackageEntry<'a>> {
move |arena, state| {
// You may optionally have a package shorthand,
// e.g. "uc" in `uc: roc/unicode 1.0.0`
@ -293,19 +245,19 @@ pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>, EPackageEntry<'a
.parse(arena, state)?;
let entry = match opt_shorthand {
Some((shorthand, spaces_after_shorthand)) => PackageEntry::Entry {
Some((shorthand, spaces_after_shorthand)) => PackageEntry {
shorthand,
spaces_after_shorthand,
package_or_path,
},
None => PackageEntry::Entry {
None => PackageEntry {
shorthand: "",
spaces_after_shorthand: &[],
package_or_path,
},
};
Ok((MadeProgress, entry, state))
Ok((MadeProgress, Spaced::Item(entry), state))
}
}

View file

@ -1,7 +1,7 @@
use crate::ast::{Collection, CommentOrNewline, Def, Module};
use crate::ast::{Collection, CommentOrNewline, Def, Module, Spaced};
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::header::{
package_entry, package_name, package_or_path, AppHeader, Effects, ExposesEntry, ImportsEntry,
package_entry, package_name, package_or_path, AppHeader, Effects, ExposedName, ImportsEntry,
InterfaceHeader, ModuleName, PackageEntry, PlatformHeader, PlatformRequires, PlatformRigid, To,
TypedIdent,
};
@ -220,7 +220,7 @@ fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> {
#[allow(clippy::type_complexity)]
let opt_imports: Option<(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Collection<'a, Located<ImportsEntry<'a>>>,
Collection<'a, Located<Spaced<'a, ImportsEntry<'a>>>>,
)> = opt_imports;
let ((before_imports, after_imports), imports) =
@ -303,7 +303,7 @@ fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> {
#[derive(Debug)]
struct ProvidesTo<'a> {
entries: Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
entries: Collection<'a, Located<Spaced<'a, ExposedName<'a>>>>,
to: Located<To<'a>>,
before_provides_keyword: &'a [CommentOrNewline<'a>],
@ -362,7 +362,7 @@ fn provides_without_to<'a>() -> impl Parser<
'a,
(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
Collection<'a, Located<Spaced<'a, ExposedName<'a>>>>,
),
EProvides<'a>,
> {
@ -385,14 +385,14 @@ fn provides_without_to<'a>() -> impl Parser<
EProvides::Open,
EProvides::Space,
EProvides::IndentListEnd,
ExposesEntry::SpaceBefore
Spaced::SpaceBefore
)
)
}
fn exposes_entry<'a, F, E>(
to_expectation: F,
) -> impl Parser<'a, Located<ExposesEntry<'a, &'a str>>, E>
) -> impl Parser<'a, Located<Spaced<'a, ExposedName<'a>>>, E>
where
F: Fn(crate::parser::Row, crate::parser::Col) -> E,
F: Copy,
@ -400,7 +400,7 @@ where
{
loc!(map!(
specialize(|_, r, c| to_expectation(r, c), unqualified_ident()),
ExposesEntry::Exposed
|n| Spaced::Item(ExposedName::new(n))
))
}
@ -444,7 +444,7 @@ fn platform_requires<'a>() -> impl Parser<'a, PlatformRequires<'a>, ERequires<'a
#[inline(always)]
fn requires_rigids<'a>(
min_indent: u16,
) -> impl Parser<'a, Collection<'a, Located<PlatformRigid<'a>>>, ERequires<'a>> {
) -> impl Parser<'a, Collection<'a, Located<Spaced<'a, PlatformRigid<'a>>>>, ERequires<'a>> {
collection_trailing_sep_e!(
word1(b'{', ERequires::ListStart),
specialize(|_, r, c| ERequires::Rigid(r, c), loc!(requires_rigid())),
@ -454,23 +454,24 @@ fn requires_rigids<'a>(
ERequires::Open,
ERequires::Space,
ERequires::IndentListEnd,
PlatformRigid::SpaceBefore
Spaced::SpaceBefore
)
}
#[inline(always)]
fn requires_rigid<'a>() -> impl Parser<'a, PlatformRigid<'a>, ()> {
fn requires_rigid<'a>() -> impl Parser<'a, Spaced<'a, PlatformRigid<'a>>, ()> {
map!(
and!(
lowercase_ident(),
skip_first!(word2(b'=', b'>', |_, _| ()), uppercase_ident())
),
|(rigid, alias)| PlatformRigid::Entry { rigid, alias }
|(rigid, alias)| Spaced::Item(PlatformRigid { rigid, alias })
)
}
#[inline(always)]
fn requires_typed_ident<'a>() -> impl Parser<'a, Located<TypedIdent<'a>>, ERequires<'a>> {
fn requires_typed_ident<'a>() -> impl Parser<'a, Located<Spaced<'a, TypedIdent<'a>>>, ERequires<'a>>
{
skip_first!(
word1(b'{', ERequires::ListStart),
skip_second!(
@ -491,7 +492,7 @@ fn exposes_values<'a>() -> impl Parser<
'a,
(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
Collection<'a, Located<Spaced<'a, ExposedName<'a>>>>,
),
EExposes,
> {
@ -515,7 +516,7 @@ fn exposes_values<'a>() -> impl Parser<
EExposes::Open,
EExposes::Space,
EExposes::IndentListEnd,
ExposesEntry::SpaceBefore
Spaced::SpaceBefore
)
)
}
@ -545,7 +546,7 @@ fn exposes_modules<'a>() -> impl Parser<
'a,
(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Collection<'a, Located<ExposesEntry<'a, ModuleName<'a>>>>,
Collection<'a, Located<Spaced<'a, ModuleName<'a>>>>,
),
EExposes,
> {
@ -569,14 +570,14 @@ fn exposes_modules<'a>() -> impl Parser<
EExposes::Open,
EExposes::Space,
EExposes::IndentListEnd,
ExposesEntry::SpaceBefore
Spaced::SpaceBefore
)
)
}
fn exposes_module<'a, F, E>(
to_expectation: F,
) -> impl Parser<'a, Located<ExposesEntry<'a, ModuleName<'a>>>, E>
) -> impl Parser<'a, Located<Spaced<'a, ModuleName<'a>>>, E>
where
F: Fn(crate::parser::Row, crate::parser::Col) -> E,
F: Copy,
@ -584,13 +585,13 @@ where
{
loc!(map!(
specialize(|_, r, c| to_expectation(r, c), module_name()),
ExposesEntry::Exposed
Spaced::Item
))
}
#[derive(Debug)]
struct Packages<'a> {
entries: Collection<'a, Located<PackageEntry<'a>>>,
entries: Collection<'a, Located<Spaced<'a, PackageEntry<'a>>>>,
before_packages_keyword: &'a [CommentOrNewline<'a>],
after_packages_keyword: &'a [CommentOrNewline<'a>],
}
@ -618,7 +619,7 @@ fn packages<'a>() -> impl Parser<'a, Packages<'a>, EPackages<'a>> {
EPackages::Open,
EPackages::Space,
EPackages::IndentListEnd,
PackageEntry::SpaceBefore
Spaced::SpaceBefore
)
),
|((before_packages_keyword, after_packages_keyword), entries): (
@ -639,7 +640,7 @@ fn imports<'a>() -> impl Parser<
'a,
(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Collection<'a, Located<ImportsEntry<'a>>>,
Collection<'a, Located<Spaced<'a, ImportsEntry<'a>>>>,
),
EImports,
> {
@ -663,7 +664,7 @@ fn imports<'a>() -> impl Parser<
EImports::Open,
EImports::Space,
EImports::IndentListEnd,
ImportsEntry::SpaceBefore
Spaced::SpaceBefore
)
)
}
@ -706,7 +707,7 @@ fn effects<'a>() -> impl Parser<'a, Effects<'a>, EEffects<'a>> {
EEffects::Open,
EEffects::Space,
EEffects::IndentListEnd,
TypedIdent::SpaceBefore
Spaced::SpaceBefore
)
.parse(arena, state)?;
@ -726,7 +727,7 @@ fn effects<'a>() -> impl Parser<'a, Effects<'a>, EEffects<'a>> {
}
#[inline(always)]
fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>, ETypedIdent<'a>> {
fn typed_ident<'a>() -> impl Parser<'a, Spaced<'a, TypedIdent<'a>>, ETypedIdent<'a>> {
// e.g.
//
// printLine : Str -> Effect {}
@ -752,11 +753,11 @@ fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>, ETypedIdent<'a>> {
)
),
|((ident, spaces_before_colon), ann)| {
TypedIdent::Entry {
Spaced::Item(TypedIdent {
ident,
spaces_before_colon,
ann,
}
})
}
)
}
@ -775,18 +776,18 @@ where
}
#[inline(always)]
fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>, EImports> {
fn imports_entry<'a>() -> impl Parser<'a, Spaced<'a, ImportsEntry<'a>>, EImports> {
let min_indent = 1;
type Temp<'a> = (
(Option<&'a str>, ModuleName<'a>),
Option<Collection<'a, Located<ExposesEntry<'a, &'a str>>>>,
Option<Collection<'a, Located<Spaced<'a, ExposedName<'a>>>>>,
);
map_with_arena!(
and!(
and!(
// e.g. `base.`
// e.g. `pf.`
maybe!(skip_second!(
shortname(),
word1(b'.', EImports::ShorthandDot)
@ -806,18 +807,20 @@ fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>, EImports> {
EImports::Open,
EImports::Space,
EImports::IndentSetEnd,
ExposesEntry::SpaceBefore
Spaced::SpaceBefore
)
))
),
|_arena, ((opt_shortname, module_name), opt_values): Temp<'a>| {
let exposed_values = opt_values.unwrap_or_else(Collection::empty);
match opt_shortname {
let entry = match opt_shortname {
Some(shortname) => ImportsEntry::Package(shortname, module_name, exposed_values),
None => ImportsEntry::Module(module_name, exposed_values),
}
};
Spaced::Item(entry)
}
)
}

View file

@ -6,7 +6,7 @@ Platform {
},
requires: PlatformRequires {
rigids: [],
signature: |L 0-0, C 38-47| Entry {
signature: |L 0-0, C 38-47| TypedIdent {
ident: |L 0-0, C 38-42| "main",
spaces_before_colon: [],
ann: |L 0-0, C 45-47| Record {

View file

@ -4,10 +4,10 @@ App {
"quicksort",
),
packages: [
|L 1-1, C 15-33| Entry {
shorthand: "base",
|L 1-1, C 15-31| PackageEntry {
shorthand: "pf",
spaces_after_shorthand: [],
package_or_path: |L 1-1, C 21-33| Path(
package_or_path: |L 1-1, C 19-31| Path(
PlainLine(
"./platform",
),
@ -24,12 +24,12 @@ App {
),
],
provides: [
|L 3-3, C 15-24| Exposed(
|L 3-3, C 15-24| ExposedName(
"quicksort",
),
],
to: |L 3-3, C 30-34| ExistingPackage(
"base",
to: |L 3-3, C 30-32| ExistingPackage(
"pf",
),
before_header: [],
after_app_keyword: [],

View file

@ -1,4 +1,4 @@
app "quicksort"
packages { base: "./platform" }
packages { pf: "./platform" }
imports [ foo.Bar.Baz ]
provides [ quicksort ] to base
provides [ quicksort ] to pf

View file

@ -4,10 +4,10 @@ App {
"quicksort",
),
packages: [
|L 1-1, C 15-33| Entry {
shorthand: "base",
|L 1-1, C 15-31| PackageEntry {
shorthand: "pf",
spaces_after_shorthand: [],
package_or_path: |L 1-1, C 21-33| Path(
package_or_path: |L 1-1, C 19-31| Path(
PlainLine(
"./platform",
),
@ -23,7 +23,7 @@ App {
Collection {
items: [
|L 3-3, C 8-11| SpaceBefore(
Exposed(
ExposedName(
"Baz",
),
[
@ -31,7 +31,7 @@ App {
],
),
|L 4-4, C 8-16| SpaceBefore(
Exposed(
ExposedName(
"FortyTwo",
),
[
@ -49,12 +49,12 @@ App {
),
],
provides: [
|L 7-7, C 15-24| Exposed(
|L 7-7, C 15-24| ExposedName(
"quicksort",
),
],
to: |L 7-7, C 31-35| ExistingPackage(
"base",
to: |L 7-7, C 31-33| ExistingPackage(
"pf",
),
before_header: [],
after_app_keyword: [],

View file

@ -1,8 +1,8 @@
app "quicksort"
packages { base: "./platform", }
packages { pf: "./platform", }
imports [ foo.Bar.{
Baz,
FortyTwo,
# I'm a happy comment
} ]
provides [ quicksort, ] to base
provides [ quicksort, ] to pf

View file

@ -6,12 +6,12 @@ Platform {
},
requires: PlatformRequires {
rigids: [
|L 1-1, C 14-26| Entry {
|L 1-1, C 14-26| PlatformRigid {
rigid: "model",
alias: "Model",
},
],
signature: |L 1-1, C 30-39| Entry {
signature: |L 1-1, C 30-39| TypedIdent {
ident: |L 1-1, C 30-34| "main",
spaces_before_colon: [],
ann: |L 1-1, C 37-39| Record {
@ -22,7 +22,7 @@ Platform {
},
exposes: [],
packages: [
|L 3-3, C 15-27| Entry {
|L 3-3, C 15-27| PackageEntry {
shorthand: "foo",
spaces_after_shorthand: [],
package_or_path: |L 3-3, C 20-27| Path(
@ -34,7 +34,7 @@ Platform {
],
imports: [],
provides: [
|L 5-5, C 15-26| Exposed(
|L 5-5, C 15-26| ExposedName(
"mainForHost",
),
],

105
compiler/test_gen/build.rs Normal file
View file

@ -0,0 +1,105 @@
use std::env;
use std::ffi::OsStr;
use std::path::Path;
use std::process::Command;
const PLATFORM_FILENAME: &str = "wasm_test_platform";
const OUT_DIR_VAR: &str = "TEST_GEN_OUT";
const LIBC_PATH_VAR: &str = "TEST_GEN_WASM_LIBC_PATH";
const COMPILER_RT_PATH_VAR: &str = "TEST_GEN_WASM_COMPILER_RT_PATH";
fn main() {
println!("cargo:rerun-if-changed=build.rs");
if feature_is_enabled("gen-wasm") {
build_wasm();
}
}
fn build_wasm() {
let out_dir = env::var("OUT_DIR").unwrap();
println!("cargo:rustc-env={}={}", OUT_DIR_VAR, out_dir);
build_wasm_test_platform(&out_dir);
build_wasm_libc(&out_dir);
}
fn build_wasm_test_platform(out_dir: &str) {
println!("cargo:rerun-if-changed=src/helpers/{}.c", PLATFORM_FILENAME);
run_command(
Path::new("."),
"zig",
[
"build-obj",
"-target",
"wasm32-wasi",
"-lc",
&format!("src/helpers/{}.c", PLATFORM_FILENAME),
&format!("-femit-bin={}/{}.o", out_dir, PLATFORM_FILENAME),
],
);
}
fn build_wasm_libc(out_dir: &str) {
let source_path = "src/helpers/dummy_libc_program.c";
println!("cargo:rerun-if-changed={}", source_path);
let cwd = Path::new(".");
let zig_cache_dir = format!("{}/zig-cache-wasm32", out_dir);
run_command(
cwd,
"zig",
[
"build-exe", // must be an executable or it won't compile libc
"-target",
"wasm32-wasi",
"-lc",
source_path,
"-femit-bin=/dev/null",
"--global-cache-dir",
&zig_cache_dir,
],
);
let libc_path = run_command(cwd, "find", [&zig_cache_dir, "-name", "libc.a"]);
let compiler_rt_path = run_command(cwd, "find", [&zig_cache_dir, "-name", "compiler_rt.o"]);
println!("cargo:rustc-env={}={}", LIBC_PATH_VAR, libc_path);
println!(
"cargo:rustc-env={}={}",
COMPILER_RT_PATH_VAR, compiler_rt_path
);
}
fn feature_is_enabled(feature_name: &str) -> bool {
let cargo_env_var = format!(
"CARGO_FEATURE_{}",
feature_name.replace("-", "_").to_uppercase()
);
env::var(cargo_env_var).is_ok()
}
fn run_command<S, I, P: AsRef<Path>>(path: P, command_str: &str, args: I) -> String
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let output_result = Command::new(OsStr::new(&command_str))
.current_dir(path)
.args(args)
.output();
match output_result {
Ok(output) => match output.status.success() {
true => std::str::from_utf8(&output.stdout).unwrap().to_string(),
false => {
let error_str = match std::str::from_utf8(&output.stderr) {
Ok(stderr) => stderr.to_string(),
Err(_) => format!("Failed to run \"{}\"", command_str),
};
panic!("{} failed: {}", command_str, error_str);
}
},
Err(reason) => panic!("{} failed: {}", command_str, reason),
}
}

View file

@ -31,7 +31,7 @@ fn nat_alias() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn i128_signed_int_alias() {
assert_evals_to!(
indoc!(
@ -115,7 +115,7 @@ fn i8_signed_int_alias() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn i128_hex_int_alias() {
assert_evals_to!(
indoc!(
@ -741,7 +741,7 @@ fn gen_int_less_than() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn gen_dec_eq() {
assert_evals_to!(
indoc!(
@ -761,7 +761,7 @@ fn gen_dec_eq() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn gen_dec_neq() {
assert_evals_to!(
indoc!(
@ -864,7 +864,7 @@ fn gen_sub_i64() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn gen_mul_dec() {
assert_evals_to!(
indoc!(
@ -1309,7 +1309,7 @@ fn num_to_float() {
}
#[test]
#[cfg(any(feature = "gen-dev"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
fn num_to_float_f64_to_f32() {
assert_evals_to!(
indoc!(
@ -1328,7 +1328,47 @@ fn num_to_float_f64_to_f32() {
}
#[test]
#[cfg(any(feature = "gen-wasm", feature = "gen-dev"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
fn num_to_float_f32_to_f32() {
assert_evals_to!(
indoc!(
r#"
arg : F32
arg = 9.0
ret : F32
ret = Num.toFloat arg
ret
"#
),
9.0,
f32
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn num_to_float_f64_to_f64() {
assert_evals_to!(
indoc!(
r#"
arg : F64
arg = 9.0
ret : F64
ret = Num.toFloat arg
ret
"#
),
9.0,
f64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn num_to_float_f32_to_f64() {
assert_evals_to!(
indoc!(
@ -1790,7 +1830,7 @@ fn shift_right_zf_by() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn max_i128() {
assert_evals_to!(
indoc!(

View file

@ -806,6 +806,24 @@ fn return_nested_record() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn nested_record_load() {
assert_evals_to!(
indoc!(
r#"
x = { a : { b : 0x5 } }
y = x.a
y.b
"#
),
5,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn accessor_twice() {

View file

@ -1,16 +1,16 @@
#![cfg(feature = "gen-llvm")]
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to;
// #[cfg(feature = "gen-dev")]
// use crate::helpers::dev::assert_evals_to;
// #[cfg(feature = "gen-wasm")]
// use crate::helpers::wasm::assert_evals_to;
#[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::assert_evals_to;
// use crate::assert_wasm_evals_to as assert_evals_to;
#[allow(unused_imports)]
use indoc::indoc;
#[allow(unused_imports)]
use roc_std::{RocList, RocStr};
#[test]
@ -31,7 +31,7 @@ fn width_and_alignment_u8_u8() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn applied_tag_nothing_ir() {
assert_evals_to!(
indoc!(
@ -51,7 +51,7 @@ fn applied_tag_nothing_ir() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn applied_tag_nothing() {
assert_evals_to!(
indoc!(
@ -71,7 +71,7 @@ fn applied_tag_nothing() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn applied_tag_just() {
assert_evals_to!(
indoc!(
@ -90,7 +90,7 @@ fn applied_tag_just() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn applied_tag_just_ir() {
assert_evals_to!(
indoc!(
@ -109,7 +109,7 @@ fn applied_tag_just_ir() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn applied_tag_just_enum() {
assert_evals_to!(
indoc!(
@ -133,7 +133,7 @@ fn applied_tag_just_enum() {
}
// #[test]
#[cfg(any(feature = "gen-llvm"))]
// #[cfg(any(feature = "gen-llvm"))]
// fn raw_result() {
// assert_evals_to!(
// indoc!(
@ -149,7 +149,7 @@ fn applied_tag_just_enum() {
// );
// }
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn true_is_true() {
assert_evals_to!(
indoc!(
@ -166,7 +166,7 @@ fn true_is_true() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn false_is_false() {
assert_evals_to!(
indoc!(
@ -205,7 +205,7 @@ fn basic_enum() {
}
// #[test]
#[cfg(any(feature = "gen-llvm"))]
// #[cfg(any(feature = "gen-llvm"))]
// fn linked_list_empty() {
// assert_evals_to!(
// indoc!(
@ -224,7 +224,7 @@ fn basic_enum() {
// }
//
// #[test]
#[cfg(any(feature = "gen-llvm"))]
// #[cfg(any(feature = "gen-llvm"))]
// fn linked_list_singleton() {
// assert_evals_to!(
// indoc!(
@ -243,7 +243,7 @@ fn basic_enum() {
// }
//
// #[test]
#[cfg(any(feature = "gen-llvm"))]
// #[cfg(any(feature = "gen-llvm"))]
// fn linked_list_is_empty() {
// assert_evals_to!(
// indoc!(
@ -264,7 +264,7 @@ fn basic_enum() {
// );
// }
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn even_odd() {
assert_evals_to!(
indoc!(
@ -290,7 +290,7 @@ fn even_odd() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn gen_literal_true() {
assert_evals_to!(
indoc!(
@ -304,7 +304,7 @@ fn gen_literal_true() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn gen_if_float() {
assert_evals_to!(
indoc!(
@ -317,7 +317,7 @@ fn gen_if_float() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn when_on_nothing() {
assert_evals_to!(
indoc!(
@ -336,7 +336,7 @@ fn when_on_nothing() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn when_on_just() {
assert_evals_to!(
indoc!(
@ -355,7 +355,7 @@ fn when_on_just() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn when_on_result() {
assert_evals_to!(
indoc!(
@ -374,7 +374,7 @@ fn when_on_result() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn when_on_these() {
assert_evals_to!(
indoc!(
@ -396,7 +396,7 @@ fn when_on_these() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn match_on_two_values() {
// this will produce a Chain internally
assert_evals_to!(
@ -413,8 +413,8 @@ fn match_on_two_values() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn pair_with_guard_pattern() {
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn pair_with_underscore() {
assert_evals_to!(
indoc!(
r#"
@ -430,8 +430,8 @@ fn pair_with_guard_pattern() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn result_with_guard_pattern() {
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn result_with_underscore() {
// This test revealed an issue with hashing Test values
assert_evals_to!(
indoc!(
@ -451,7 +451,7 @@ fn result_with_guard_pattern() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn maybe_is_just_not_nested() {
assert_evals_to!(
indoc!(
@ -476,7 +476,7 @@ fn maybe_is_just_not_nested() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn maybe_is_just_nested() {
assert_evals_to!(
indoc!(
@ -498,7 +498,7 @@ fn maybe_is_just_nested() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn nested_pattern_match() {
assert_evals_to!(
indoc!(
@ -535,7 +535,7 @@ fn if_guard_vanilla() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[ignore]
fn when_on_single_value_tag() {
// this fails because the switched-on symbol is not defined
@ -711,7 +711,7 @@ fn if_guard_exhaustiveness() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn when_on_enum() {
assert_evals_to!(
indoc!(
@ -733,7 +733,7 @@ fn when_on_enum() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn pattern_matching_unit() {
assert_evals_to!(
indoc!(
@ -792,7 +792,7 @@ fn pattern_matching_unit() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn one_element_tag() {
assert_evals_to!(
indoc!(
@ -809,7 +809,7 @@ fn one_element_tag() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn nested_tag_union() {
assert_evals_to!(
indoc!(
@ -830,7 +830,7 @@ fn nested_tag_union() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn unit_type() {
assert_evals_to!(
indoc!(
@ -849,25 +849,7 @@ fn unit_type() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn nested_record_load() {
assert_evals_to!(
indoc!(
r#"
x = { a : { b : 0x5 } }
y = x.a
y.b
"#
),
5,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn join_point_if() {
assert_evals_to!(
indoc!(
@ -884,7 +866,7 @@ fn join_point_if() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn join_point_when() {
assert_evals_to!(
indoc!(
@ -910,7 +892,7 @@ fn join_point_when() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn join_point_with_cond_expr() {
assert_evals_to!(
indoc!(
@ -949,7 +931,7 @@ fn join_point_with_cond_expr() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn alignment_in_single_tag_construction() {
assert_evals_to!(indoc!("Three (1 == 1) 32"), (32i64, true), (i64, bool));
@ -961,7 +943,7 @@ fn alignment_in_single_tag_construction() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn alignment_in_single_tag_pattern_match() {
assert_evals_to!(
indoc!(
@ -993,7 +975,7 @@ fn alignment_in_single_tag_pattern_match() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn alignment_in_multi_tag_construction_two() {
assert_evals_to!(
indoc!(
@ -1011,7 +993,7 @@ fn alignment_in_multi_tag_construction_two() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn alignment_in_multi_tag_construction_three() {
assert_evals_to!(
indoc!(
@ -1028,7 +1010,7 @@ fn alignment_in_multi_tag_construction_three() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn alignment_in_multi_tag_pattern_match() {
assert_evals_to!(
indoc!(
@ -1067,7 +1049,7 @@ fn alignment_in_multi_tag_pattern_match() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[ignore]
fn phantom_polymorphic() {
// see https://github.com/rtfeldman/roc/issues/786 and below
@ -1093,7 +1075,7 @@ fn phantom_polymorphic() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[ignore]
fn phantom_polymorphic_record() {
// see https://github.com/rtfeldman/roc/issues/786
@ -1143,7 +1125,7 @@ fn result_never() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn nested_recursive_literal() {
assert_evals_to!(
indoc!(
@ -1248,7 +1230,7 @@ fn applied_tag_function_linked_list() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic(expected = "")]
fn tag_must_be_its_own_type() {
assert_evals_to!(

View file

@ -0,0 +1,7 @@
#include <stdio.h>
void main() {
printf("Hello, I am a C program and I use libc.\n");
printf("If you compile me, you'll compile libc too. That's handy for cross-compilation including Wasm.\n");
printf("Use `zig build-exe` with `--global-cache-dir my/build/directory` to put libc.a where you want it.\n");
}

View file

@ -27,7 +27,7 @@ macro_rules! from_wasm_memory_primitive_decode {
let raw_ptr = ptr as *mut u8;
let slice = unsafe { std::slice::from_raw_parts_mut(raw_ptr, width) };
let ptr: wasmer::WasmPtr<u8, wasmer::Array> = wasmer::WasmPtr::new(offset as u32);
let ptr: wasmer::WasmPtr<u8, wasmer::Array> = wasmer::WasmPtr::new(offset);
let foobar = (ptr.deref(memory, 0, width as u32)).unwrap();
let wasm_slice = unsafe { std::mem::transmute(foobar) };

View file

@ -1,31 +1,27 @@
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use std::cell::Cell;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use tempfile::{tempdir, TempDir};
use wasmer::Memory;
use crate::helpers::from_wasm32_memory::FromWasm32Memory;
use crate::helpers::wasm32_test_result::Wasm32TestResult;
use roc_builtins::bitcode;
use roc_can::builtins::builtin_defs_map;
use roc_collections::all::{MutMap, MutSet};
use roc_gen_wasm::wasm_module::linking::{WasmObjectSymbol, WASM_SYM_UNDEFINED};
use roc_gen_wasm::wasm_module::sections::{Import, ImportDesc};
use roc_gen_wasm::wasm_module::{
CodeBuilder, Export, ExportType, LocalId, Signature, SymInfo, ValueType, WasmModule,
};
use roc_gen_wasm::MEMORY_NAME;
// Should manually match build.rs
const PLATFORM_FILENAME: &str = "wasm_test_platform";
const OUT_DIR_VAR: &str = "TEST_GEN_OUT";
const LIBC_PATH_VAR: &str = "TEST_GEN_WASM_LIBC_PATH";
const COMPILER_RT_PATH_VAR: &str = "TEST_GEN_WASM_COMPILER_RT_PATH";
#[allow(unused_imports)]
use roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS;
const TEST_WRAPPER_NAME: &str = "test_wrapper";
std::thread_local! {
static TEST_COUNTER: Cell<u32> = Cell::new(0);
}
fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
@ -44,7 +40,7 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
arena: &'a bumpalo::Bump,
src: &str,
stdlib: &'a roc_builtins::std::StdLib,
_result_type_dummy: &T,
_result_type_dummy: PhantomData<T>,
) -> wasmer::Instance {
use std::path::{Path, PathBuf};
@ -124,9 +120,6 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
T::insert_test_wrapper(arena, &mut wasm_module, TEST_WRAPPER_NAME, main_fn_index);
// We can either generate the test platform or write an external source file, whatever works
generate_test_platform(&mut wasm_module, arena);
let mut module_bytes = std::vec::Vec::with_capacity(4096);
wasm_module.serialize_mut(&mut module_bytes);
@ -136,14 +129,14 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
let store = Store::default();
// Keep the final .wasm file for debugging with wasm-objdump or wasm2wat
const DEBUG_WASM_FILE: bool = false;
// Keep the output binary for debugging with wasm2wat, wasm-objdump, wasm-validate, wasmer...
const KEEP_WASM_FILE: bool = false;
let wasmer_module = {
let tmp_dir: TempDir; // directory for normal test runs, deleted when dropped
let debug_dir: String; // persistent directory for debugging
let wasm_build_dir: &Path = if DEBUG_WASM_FILE {
let wasm_build_dir: &Path = if KEEP_WASM_FILE {
// Directory name based on a hash of the Roc source
let mut hash_state = DefaultHasher::new();
src.hash(&mut hash_state);
@ -162,7 +155,10 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
let final_wasm_file = wasm_build_dir.join("final.wasm");
let app_o_file = wasm_build_dir.join("app.o");
let libc_a_file = "../gen_wasm/lib/libc.a";
let test_out_dir = std::env::var(OUT_DIR_VAR).unwrap();
let test_platform_o = format!("{}/{}.o", test_out_dir, PLATFORM_FILENAME);
let libc_a_file = std::env::var(LIBC_PATH_VAR).unwrap();
let compiler_rt_o_file = std::env::var(COMPILER_RT_PATH_VAR).unwrap();
// write the module to a file so the linker can access it
std::fs::write(&app_o_file, &module_bytes).unwrap();
@ -172,7 +168,9 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
// input files
app_o_file.to_str().unwrap(),
bitcode::BUILTINS_WASM32_OBJ_PATH,
libc_a_file,
&test_platform_o,
&libc_a_file,
&compiler_rt_o_file,
// output
"-o",
final_wasm_file.to_str().unwrap(),
@ -221,7 +219,7 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
}
#[allow(dead_code)]
pub fn assert_wasm_evals_to_help<T>(src: &str, expected: T) -> Result<T, String>
pub fn assert_wasm_evals_to_help<T>(src: &str, phantom: PhantomData<T>) -> Result<T, String>
where
T: FromWasm32Memory + Wasm32TestResult,
{
@ -230,7 +228,7 @@ where
// NOTE the stdlib must be in the arena; just taking a reference will segfault
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
let instance = crate::helpers::wasm::helper_wasm(&arena, src, stdlib, &expected);
let instance = crate::helpers::wasm::helper_wasm(&arena, src, stdlib, phantom);
let memory = instance.exports.get_memory(MEMORY_NAME).unwrap();
@ -244,6 +242,16 @@ where
_ => panic!(),
};
if false {
println!("test_wrapper returned 0x{:x}", address);
println!("Stack:");
crate::helpers::wasm::debug_memory_hex(memory, address, std::mem::size_of::<T>());
}
if false {
println!("Heap:");
// Manually provide address and size based on printf in wasm_test_platform.c
crate::helpers::wasm::debug_memory_hex(memory, 0x11440, 24);
}
let output = <T as FromWasm32Memory>::decode(memory, address as u32);
Ok(output)
@ -251,10 +259,41 @@ where
}
}
/// Print out hex bytes of the test result, and a few words on either side
/// Can be handy for debugging misalignment issues etc.
pub fn debug_memory_hex(memory: &Memory, address: i32, size: usize) {
let memory_words: &[u32] = unsafe {
let memory_bytes = memory.data_unchecked();
std::mem::transmute(memory_bytes)
};
let extra_words = 2;
let result_start = (address as usize) / 4;
let result_end = result_start + ((size + 3) / 4);
let start = result_start - extra_words;
let end = result_end + extra_words;
for index in start..end {
let result_marker = if index >= result_start && index < result_end {
"|"
} else {
" "
};
println!(
"{:x} {} {:08x}",
index * 4,
result_marker,
memory_words[index]
);
}
println!();
}
#[allow(unused_macros)]
macro_rules! assert_wasm_evals_to {
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
match $crate::helpers::wasm::assert_wasm_evals_to_help::<$ty>($src, $expected) {
let phantom = std::marker::PhantomData;
match $crate::helpers::wasm::assert_wasm_evals_to_help::<$ty>($src, phantom) {
Err(msg) => panic!("{:?}", msg),
Ok(actual) => {
assert_eq!($transform(actual), $expected)
@ -298,123 +337,3 @@ pub fn identity<T>(value: T) -> T {
pub(crate) use assert_evals_to;
#[allow(unused_imports)]
pub(crate) use assert_wasm_evals_to;
fn wrap_libc_fn<'a>(
module: &mut WasmModule<'a>,
arena: &'a Bump,
roc_name: &'a str,
libc_name: &'a str,
params: &'a [(ValueType, bool)],
ret_type: Option<ValueType>,
) {
let symbol_table = module.linking.symbol_table_mut();
// Type signatures
let mut wrapper_signature = Signature {
param_types: Vec::with_capacity_in(params.len(), arena),
ret_type,
};
let mut libc_signature = Signature {
param_types: Vec::with_capacity_in(params.len(), arena),
ret_type,
};
for (ty, used) in params.iter() {
wrapper_signature.param_types.push(*ty);
if *used {
libc_signature.param_types.push(*ty);
}
}
/*
* Import a function from libc
*/
let libc_signature_index = module.types.insert(libc_signature);
// Import
let import_index = module.import.entries.len() as u32;
module.import.entries.push(Import {
module: "env",
name: libc_name.to_string(),
description: ImportDesc::Func {
signature_index: libc_signature_index,
},
});
// Linker info
let libc_sym_idx = symbol_table.len() as u32;
symbol_table.push(SymInfo::Function(WasmObjectSymbol::Imported {
flags: WASM_SYM_UNDEFINED,
index: import_index,
}));
/*
* Export a wrapper function
*/
// Declaration
let wrapper_sig_index = module.types.insert(wrapper_signature);
module.function.signature_indices.push(wrapper_sig_index);
// Body
let mut code_builder = CodeBuilder::new(arena);
let mut num_libc_args = 0;
for (i, (_, used)) in params.iter().enumerate() {
if *used {
code_builder.get_local(LocalId(i as u32));
num_libc_args += 1;
}
}
code_builder.call(
import_index,
libc_sym_idx,
num_libc_args,
ret_type.is_some(),
);
code_builder.build_fn_header(&[], 0, None);
let wrapper_index = module.code.code_builders.len() as u32;
module.code.code_builders.push(code_builder);
// Export
module.export.entries.push(Export {
name: roc_name.to_string(),
ty: ExportType::Func,
index: wrapper_index,
});
// Linker symbol
symbol_table.push(SymInfo::Function(WasmObjectSymbol::Defined {
flags: 0,
index: wrapper_index,
name: roc_name.to_string(),
}));
}
fn generate_test_platform<'a>(module: &mut WasmModule<'a>, arena: &'a Bump) {
use ValueType::I32;
wrap_libc_fn(
module,
arena,
"roc_alloc",
"malloc",
// only the first argument of roc_alloc is passed to malloc
&[(I32, true), (I32, false)],
Some(I32),
);
wrap_libc_fn(
module,
arena,
"roc_dealloc",
"free",
&[(I32, true), (I32, false)],
None,
);
wrap_libc_fn(
module,
arena,
"roc_realloc",
"realloc",
&[(I32, true), (I32, false), (I32, true), (I32, false)],
Some(I32),
);
}

View file

@ -0,0 +1,64 @@
#include <stdio.h>
// If any printf is included for compilation, even if unused, test runs take 50% longer
#define DEBUG 0
//--------------------------
void *roc_alloc(size_t size, unsigned int alignment)
{
void *allocated = malloc(size);
#if DEBUG
if (!allocated)
{
fprintf(stderr, "roc_alloc failed\n");
exit(1);
}
else
{
printf("roc_alloc allocated %d bytes at %p\n", size, allocated);
}
#endif
return allocated;
}
//--------------------------
void *roc_realloc(void *ptr, size_t new_size, size_t old_size,
unsigned int alignment)
{
return realloc(ptr, new_size);
}
//--------------------------
void roc_dealloc(void *ptr, unsigned int alignment)
{
free(ptr);
}
//--------------------------
void roc_panic(void *ptr, unsigned int alignment)
{
#if DEBUG
char *msg = (char *)ptr;
fprintf(stderr,
"Application crashed with message\n\n %s\n\nShutting down\n", msg);
#endif
exit(1);
}
//--------------------------
void *roc_memcpy(void *dest, const void *src, size_t n)
{
return memcpy(dest, src, n);
}
//--------------------------
void *roc_memset(void *str, int c, size_t n)
{
return memset(str, c, n);
}

View file

@ -58,9 +58,9 @@ mod insert_doc_syntax_highlighting {
pub const HELLO_WORLD: &str = r#"
app "test-app"
packages { base: "platform" }
packages { pf: "platform" }
imports []
provides [ main ] to base
provides [ main ] to pf
main = "Hello, world!"

View file

@ -1,7 +1,7 @@
The editor is a work in progress, only a limited subset of Roc expressions are currently supported.
Unlike most editors, we use projectional or structural editing to edit the [Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) directly. This will allow for cool features like excellent auto-complete and refactoring.
Unlike most editors, we use projectional or structural editing to edit the [Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) directly. This will allow for cool features like excellent auto-complete, refactoring and never needing to format your code.
## Getting started
@ -29,3 +29,46 @@ We thank the following open source projects in particular for inspiring us when
- [rgx](https://github.com/cloudhead/rgx)
- [elm-editor](https://github.com/jxxcarlson/elm-editor)
- [iced](https://github.com/hecrj/iced)
## How does it work?
To take a look behind the scenes, open the editor with `./roc edit` or `cargo run edit` and press F11.
This debug view shows important data structures that can be found in `editor/src/editor/mvc/ed_model.rs`.
Add or delete some code to see how these data structures are updated.
From roc to render:
- `./roc edit` or `cargo run edit` is first handled in `cli/src/main.rs`, from there the editor's launch function is called (`editor/src/editor/main.rs`).
- In `run_event_loop` we initialize the winit window, wgpu, the editor's model(`EdModel`) and start the rendering loop.
- The `ed_model` is filled in part with data obtained by loading and typechecking the roc file with the same function (`load_and_typecheck`) that is used by the compiler.
- `ed_model` also contains an `EdModule`, which holds the parsed abstract syntax tree (AST).
- In the `init_model` function:
+ The AST is converted into a tree of `MarkupNode`. The different types of `MarkupNode` are similar to the elements/nodes in HTML. A line of roc code is represented as a nested `MarkupNode` containing mostly text `MarkupNode`s. The line `foo = "bar"` is represented as
three text `MarkupNode` representing `foo`, ` = ` and `bar`. Multiple lines of roc code are represented as nested `MarkupNode` that contain other nested `MarkupNode`.
+ `CodeLines` holds a `Vec` of `String`, each line of code is a `String`. When saving the file, the content of `CodeLines` is written to disk.
+ `GridNodeMap` maps every position of a char of roc code to a `MarkNodeId`, for easy interaction with the caret.
- Back in `editor/src/editor/main.rs` we convert the `EdModel` to `RenderedWgpu` by calling `model_to_wgpu`.
- The `RenderedWgpu` is passed to the `glyph_brush` to draw the characters(glyphs) on the screen.
### Important files
To understand how the editor works it is useful to know the most important files:
- editor/src/editor/main.rs
- editor/src/editor/mvc/ed_update.rs
- editor/src/editor/mvc/ed_model.rs
- editor/src/editor/mvc/ed_view.rs
- editor/src/editor/render_ast.rs
- editor/src/editor/render_debug.rs
Important folders/files outside the editor folder:
- code_markup/src/markup/convert
- code_markup/src/markup/nodes.rs
- ast/src/lang/core/def
- ast/src/lang/core/expr
- ast/src/lang/core/ast.rs
- ast/src/lang/env.rs
## Contributing
We welcome new contributors :heart: and are happy to help you get started.
Check [CONTRIBUTING.md](../CONTRIBUTING.md) for more info.

View file

@ -47,6 +47,8 @@ Nice collection of research on innovative editors, [link](https://futureofcoding
* [whitebox debug visualization](https://vimeo.com/483795097)
* [Hest](https://ivanish.ca/hest-time-travel/) tool for making highly interactive simulations.
* [replit](https://replit.com/) collaborative browser based IDE.
* [paper](https://openreview.net/pdf?id=SJeqs6EFvB) on finding and fixing bugs automatically.
* [specialized editors that can be embedded in main editor](https://elliot.website/editor/)
* Say you have a failing test that used to work, it would be very valuable to see all code that was changed that was used only by that test.
e.g. you have a test `calculate_sum_test` that only uses the function `add`, when the test fails you should be able to see a diff showing only what changed for the function `add`. It would also be great to have a diff of [expression values](https://homepages.cwi.nl/~storm/livelit/images/bret.png) Bret Victor style. An ambitious project would be to suggest or automatically try fixes based on these diffs.
* I think it could be possible to create a minimal reproduction of a program / block of code / code used by a single test. So for a failing unit test I would expect it to extract imports, the platform, types and functions that are necessary to run only that unit test and put them in a standalone roc project. This would be useful for sharing bugs with library+application authors and colleagues, for profiling or debugging with all "clutter" removed.
@ -56,6 +58,11 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
* We previuously mentioned showing expression values next to the code. I think when debugging it would be valuable to focus more on these valuas/data. A possible way to do this would be to create scrollable view(without need to jump between files) of inputs and outputs of user defined functions. Clicking on a function could then show the code with the expression values side by side. Having a good overview of how the values change could make it easy to find where exactly things go wrong.
- (Machine learning) algorithms to extract and show useful information from debug values.
- Ability to mark e.g. a specific record field for tracking(filter out the noise) that is being repeatedly updated throughout the program.
- Ability to collapse/fold debug output coming from specific line.
- search bar to search through printed logs
- Turn an error listed in the console into editable section of code for easy quick fixing.
- Clickable backtrace of functions, user defined functions should be made extra visible.
- VR debugging: render massive curved screen with rectangle showing code (and expression values) for every function in call stack.
### Cool regular editors
@ -126,7 +133,11 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
* show example of how to use library function Foo
* open google/github/duckduckgo search for error...
* show editor plugins for library X
* commands to control log filtering
* collaps all arms of when
* "complex" filtered search: search for all occurrences of `"#` but ignore all like `"#,`
* color this debug print orange
* remove unused imports
#### Inspiration
@ -142,6 +153,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
* Show Roc cheat sheet on start-up.
* Plugin that translates short pieces of code from another programming language to Roc. [Relevant research](https://www.youtube.com/watch?v=xTzFJIknh7E). Someone who only knows the R language could get started with Roc with less friction if they could quickly define a list R style (`lst <- c(1,2,3)`) and get it translated to Roc.
* Being able to asses or ask the user for the amount of experience they have with Roc would be a valuable feature for recommending plugins, editor tips, recommending tutorials, automated error search (e.g searching common beginner errors first), ... .
* Adjust UI based on beginner/novice/expert?
### Productivity features
@ -187,6 +199,18 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
* Feature to automatically minimize visibility(exposing values/functions/...) based on usage in tests. Suggested changes can be shown to the user for fine-grained control.
* Locally record file/function navigation behavior to offer suggestions where to navigate next. With user permission, this navigation behavior can be shared with their team so that e.g. new members get offered useful suggestions on navigating to the next relevant file.
* Intelligent search: "search this folder for <term>", "search all tests for <term>"
* Show some kind of warning if path str in code does not exist locally.
* repl on panic/error: ability to inspect all values and try executing some things at the location of the error.
* show values in memory on panic/error
* automatic clustering of (text) search results in groups by similarity
* fill screen with little windows of clustered search results
* clustering of examples similar to current code
* ability to easily screenshot a subwindow -> create static duplicate of subwindow
* Show references is a common editor feature, often I only want to see non-recursive references in the case of a recursive function.
* ability to add error you were stuck on but have now solved to error database, to help others in the future.
* For quick navigation and good overview: whole file should be shown as folded tree showing only top level defs. Hovering with mouse should allow you to show and traverse the branches, with a click to keep this view. See also ginkowriter.
* clicking on any output should take you to the place in the code where that output was printed and/or calculated.
* ability to edit printed output in such a way that the appropriate changes are made in the code that produced it. Example: edit json key in output-> code is changed to print this new key.
#### Autocomplete
@ -212,6 +236,9 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
* [NextJournal](https://nextjournal.com/joe-loco/command-bar?token=DpU6ewNQnLhYtVkwhs9GeX) Discoverable commands and shortcuts.
* [Code Ribbon](https://web.eecs.utk.edu/~azh/blog/coderibbon.html) fast navigation between files. Feature suggestion: top and down are filled with suggested files, whereas left and right are manually filled.
* [Automatic data transformation based on examples](https://youtu.be/Ej91F1fpmEw). Feature suggestion: use in combination with voice commands: e.g. "only keep time from list of datetimes".
* [Codesee](https://www.codesee.io/) code base visualization.
* [Loopy](https://dl.acm.org/doi/10.1145/3485530?sid=SCITRUS) interactive program synthesis.
* [bracket guides](https://mobile.twitter.com/elyktrix/status/1461380028609048576)
### Non-Code Related Inspiration
@ -250,10 +277,13 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
* Select a function to record.
* Do a normal run, and save the input and output of the selected function.
* Generate a unit test with that input-output pair
* [vitest](https://twitter.com/antfu7/status/1468233216939245579) only run tests that could possibly have changed (because the code they test/use has changed)
* Ability to show in sidebar if code is tested by a test. Clicking on the test in the sidebar should bring you to that test.
### Inspiration
* [Haskell language server plugin](https://github.com/haskell/haskell-language-server/blob/master/plugins/hls-eval-plugin/README.md) evaluate code in comments, to test and document functions and to quickly evaluate small expressions.
* [Hazel live test](https://mobile.twitter.com/disconcision/status/1459933500656730112)
## Documentation
@ -265,6 +295,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
* Library should have cheat sheet with most used/important docs summarized.
* With explicit user permission, anonymously track viewing statistics for documentation. Can be used to show most important documentation, report pain points to library authors.
* Easy side-by-side docs for multiple versions of library.
* ability to add questions and answers to library documentation
## Tutorials
@ -277,6 +308,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
* Plugin to translate linux commands like curl to Roc code
* Plugin to view diff between two texts
* Plugin to present codebase to new developer or walk co-worker through a problem. Records sequence of filenames and line numbers.
### Inspiration
@ -339,7 +371,7 @@ Thoughts and ideas possibly taken from above inspirations or separate.
They rely on eye tracking to move mouse cursor arond.
If we employ _some_ voice recognition functions we could make same interface as we could do for consoles where 4+2 buttons and directional pad would suffice.
That is 10 phrases that need to be pulled trough as many possible translations so people don't have to pretend that they are from Maine or Texas so they get voice recognition to work. Believe me I was there with Apple's Siri :D That is why we have 10 phrases for movement and management and most basic syntax.
* Builtin fonts that can be read more easily by those with dyslexia.
* Nice backtraces that highlight important information
* Ability to show import connection within project visually
@ -358,3 +390,5 @@ Thoughts and ideas possibly taken from above inspirations or separate.
* Code coverage visualization: allow to display code in different color when it is covered by test.
* Make "maximal privacy version" of editor available for download, next to regular version. This version would not be capable of sharing any usage/user data.
* Live code view with wasm editor. This saves bandwidth when pairing.
* [Gingkowriter](https://gingkowriter.com/) structured writing app.
* Performance improvement recommendation: show if code is eligible for tail call optimization or can do in place mutation.

View file

@ -41,7 +41,13 @@ Fish hooks are used when subvariants should be created e.g.: <collection> means
- command: current date/datetime
+ example: current datetime >> `now <- Time.now\n`
- command: list range 1 to 5
+ example: >> [ 1, 2, 3, 4, 5 ]
+ example: [ 1, 2, 3, 4, 5 ]
- command: use commandline args
- command: post/get/put request
- command: extract float(s)/number/emal addresses from string. regex match float/number/email address/...
- command: execute (bash) command/script
- command: cast/convert/parse list of x to list of y
- command: pattern match/ match/ switch/ case
## AST aware snippets
@ -54,7 +60,7 @@ Snippets are inserted based on type of value on which the cursor is located.
* We show a list with all builtin functions for the List type
* User chooses contains
* We change code to `List.contains people |Blank`
- command: Str to charlist
- command: Str to chars/charlist
## Snippets with Typed Holes
@ -72,11 +78,18 @@ Snippets are inserted based on type of value on which the cursor is located.
- command: remove/create file
- command: read/write from file
- command: concatenate strings
- command: trim (newlines) at end/start/right/left
- command: evaluate predicate for all in slice/list/array
- command: get element at index
- command: get char at index
- command: reverse stirng
- command: lambda/anonymous function
- we should auto create type hole commands for all builtins.
+ example: List has builtins reverse, repeat, len... generated snippet commands should be:
* reverse list > List.reverse ^List *^
* repeat list > List.repeat ^elem^ ^Nat^
* len list (fuzzy matches should be length of list)
- append element to list
# fuzzy matching
@ -84,7 +97,7 @@ Snippets are inserted based on type of value on which the cursor is located.
- hashmap > Dict
- map > map (function), Dict
- for > map, mapWithIndex, walk, walkBackwards, zip
- apply > map
- apply/for yield > map
- fold > walk, walkBackwards
- foldl > walkBackwards
- foldr > walk
@ -97,3 +110,4 @@ Snippets are inserted based on type of value on which the cursor is located.
- [github copilot](https://copilot.github.com/) snippet generation with machine learning
- [stackoverflow](https://stackoverflow.com)
- [rosetta code](http://www.rosettacode.org/wiki/Rosetta_Code) snippets in many different programming languages. Many [snippets](https://www.rosettacode.org/wiki/Category:Programming_Tasks) are programming contest style problems, but there also problems that demonstrate the use of JSON, SHA-256, read a file line by line...
- check docs of popular languages to cross reference function/snippet names for fuzzy matching

View file

@ -17,9 +17,9 @@ For convenience and consistency, there is only one way to format roc.
pub const HELLO_WORLD: &str = r#"
app "test-app"
packages { base: "platform" }
packages { pf: "platform" }
imports []
provides [ main ] to base
provides [ main ] to pf
main = "Hello, world!"

View file

@ -1,7 +1,7 @@
app "cfold"
packages { base: "platform" }
imports [base.Task]
provides [ main ] to base
packages { pf: "platform" }
imports [pf.Task]
provides [ main ] to pf
# adapted from https://github.com/koka-lang/koka/blob/master/test/bench/haskell/cfold.hs

View file

@ -1,7 +1,7 @@
app "closure"
packages { base: "platform" }
imports [base.Task]
provides [ main ] to base
packages { pf: "platform" }
imports [pf.Task]
provides [ main ] to pf
# see https://github.com/rtfeldman/roc/issues/985

View file

@ -1,7 +1,7 @@
app "deriv"
packages { base: "platform" }
imports [base.Task]
provides [ main ] to base
packages { pf: "platform" }
imports [pf.Task]
provides [ main ] to pf
# based on: https://github.com/koka-lang/koka/blob/master/test/bench/haskell/deriv.hs

View file

@ -1,7 +1,7 @@
app "nqueens"
packages { base: "platform" }
imports [base.Task]
provides [ main ] to base
packages { pf: "platform" }
imports [pf.Task]
provides [ main ] to pf
main : Task.Task {} []
main =

View file

@ -1,7 +1,7 @@
app "quicksortapp"
packages { base: "platform" }
imports [base.Task, Quicksort]
provides [ main ] to base
packages { pf: "platform" }
imports [pf.Task, Quicksort]
provides [ main ] to pf
main : Task.Task {} []
main =

View file

@ -1,7 +1,7 @@
app "rbtree-ck"
packages { base: "platform" }
imports [base.Task]
provides [ main ] to base
packages { pf: "platform" }
imports [pf.Task]
provides [ main ] to pf
Color : [ Red, Black ]

View file

@ -1,7 +1,7 @@
app "rbtree-del"
packages { base: "platform" }
imports [base.Task]
provides [ main ] to base
packages { pf: "platform" }
imports [pf.Task]
provides [ main ] to pf
Color : [ Red, Black ]

View file

@ -1,7 +1,7 @@
app "rbtree-insert"
packages { base: "platform" }
imports [base.Task]
provides [ main ] to base
packages { pf: "platform" }
imports [pf.Task]
provides [ main ] to pf
main : Task.Task {} []
main =

View file

@ -1,7 +1,7 @@
app "test-astar"
packages { base: "platform" }
imports [base.Task, AStar]
provides [ main ] to base
packages { pf: "platform" }
imports [pf.Task, AStar]
provides [ main ] to pf
main : Task.Task {} []
main =

View file

@ -1,7 +1,7 @@
app "test-base64"
packages { base: "platform" }
imports [base.Task, Base64 ]
provides [ main ] to base
packages { pf: "platform" }
imports [pf.Task, Base64 ]
provides [ main ] to pf
IO a : Task.Task a []

View file

@ -1,9 +1,9 @@
#!/usr/bin/env roc
app "echo"
packages { base: "platform" }
imports [ base.Task.{ Task, await }, base.Stdout, base.Stdin ]
provides [ main ] to base
packages { pf: "platform" }
imports [ pf.Task.{ Task, await }, pf.Stdout, pf.Stdin ]
provides [ main ] to pf
main : Task {} *
main =

View file

@ -2,7 +2,7 @@
use core::alloc::Layout;
use core::ffi::c_void;
use core::mem::MaybeUninit;
use core::mem::{ManuallyDrop, MaybeUninit};
use libc;
use roc_std::RocStr;
use std::ffi::CStr;
@ -119,13 +119,8 @@ pub extern "C" fn roc_fx_getLine() -> RocStr {
}
#[no_mangle]
pub extern "C" fn roc_fx_putLine(line: RocStr) -> () {
pub extern "C" fn roc_fx_putLine(line: ManuallyDrop<RocStr>) {
let bytes = line.as_slice();
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
println!("{}", string);
// don't mess with the refcount!
core::mem::forget(line);
()
}

View file

@ -1,7 +1,7 @@
app "effect-example"
packages { base: "thing/platform-dir" }
packages { pf: "thing/platform-dir" }
imports [fx.Effect]
provides [ main ] to base
provides [ main ] to pf
main : Effect.Effect {}
main =

View file

@ -1,6 +1,6 @@
interface Context
exposes [ Context, Data, with, getChar, Option, pushStack, popStack, toStr, inWhileScope ]
imports [ base.File, base.Task.{ Task }, Variable.{ Variable } ]
imports [ pf.File, pf.Task.{ Task }, Variable.{ Variable } ]
Option a : [ Some a, None ]

View file

@ -1,8 +1,8 @@
#!/usr/bin/env roc
app "false"
packages { base: "platform" }
imports [ base.Task.{ Task }, base.Stdout, base.Stdin, Context.{ Context }, Variable.{ Variable } ]
provides [ main ] to base
packages { pf: "platform" }
imports [ pf.Task.{ Task }, pf.Stdout, pf.Stdin, Context.{ Context }, Variable.{ Variable } ]
provides [ main ] to pf
# An interpreter for the False programming language: https://strlen.com/false-language/
# This is just a silly example to test this variety of program.

View file

@ -2,7 +2,7 @@
use core::alloc::Layout;
use core::ffi::c_void;
use core::mem::MaybeUninit;
use core::mem::{ManuallyDrop, MaybeUninit};
use libc;
use roc_std::{RocList, RocStr};
use std::env;
@ -141,29 +141,19 @@ pub extern "C" fn roc_fx_getChar() -> u8 {
}
#[no_mangle]
pub extern "C" fn roc_fx_putLine(line: RocStr) -> () {
pub extern "C" fn roc_fx_putLine(line: ManuallyDrop<RocStr>) {
let bytes = line.as_slice();
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
println!("{}", string);
std::io::stdout().lock().flush();
// don't mess with the refcount!
core::mem::forget(line);
()
}
#[no_mangle]
pub extern "C" fn roc_fx_putRaw(line: RocStr) -> () {
pub extern "C" fn roc_fx_putRaw(line: ManuallyDrop<RocStr>) {
let bytes = line.as_slice();
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
print!("{}", string);
std::io::stdout().lock().flush();
// don't mess with the refcount!
core::mem::forget(line);
()
}
#[no_mangle]
@ -190,25 +180,23 @@ pub extern "C" fn roc_fx_getFileBytes(br_ptr: *mut BufReader<File>) -> RocList<u
}
#[no_mangle]
pub extern "C" fn roc_fx_closeFile(br_ptr: *mut BufReader<File>) -> () {
pub extern "C" fn roc_fx_closeFile(br_ptr: *mut BufReader<File>) {
unsafe {
Box::from_raw(br_ptr);
}
}
#[no_mangle]
pub extern "C" fn roc_fx_openFile(name: RocStr) -> *mut BufReader<File> {
pub extern "C" fn roc_fx_openFile(name: ManuallyDrop<RocStr>) -> *mut BufReader<File> {
let f = File::open(name.as_str()).expect("Unable to open file");
let br = BufReader::new(f);
// don't mess with the refcount!
core::mem::forget(name);
Box::into_raw(Box::new(br))
}
#[no_mangle]
pub extern "C" fn roc_fx_withFileOpen(name: RocStr, buffer: *const u8) -> () {
pub extern "C" fn roc_fx_withFileOpen(name: ManuallyDrop<RocStr>, buffer: *const u8) {
// TODO: figure out accepting a closure in an fx and passing data to it.
// let f = File::open(name.as_str()).expect("Unable to open file");
// let mut br = BufReader::new(f);
@ -216,9 +204,4 @@ pub extern "C" fn roc_fx_withFileOpen(name: RocStr, buffer: *const u8) -> () {
// let closure_data_ptr = buffer.offset(8);
// call_the_closure(closure_data_ptr);
// }
// // don't mess with the refcount!
// core::mem::forget(name);
()
}

View file

@ -1,7 +1,7 @@
app "fib"
packages { base: "platform" }
packages { pf: "platform" }
imports []
provides [ main ] to base
provides [ main ] to pf
main = \n -> fib n 0 1

View file

@ -1,7 +1,7 @@
app "hello-rust"
packages { base: "platform" }
packages { pf: "platform" }
imports []
provides [ main ] to base
provides [ main ] to pf
greeting =
hi = "Hello"

View file

@ -1,7 +1,7 @@
app "hello-swift"
packages { base: "platform" }
packages { pf: "platform" }
imports []
provides [ main ] to base
provides [ main ] to pf
main =
host = "Swift"

View file

@ -1,7 +1,7 @@
app "hello-web"
packages { base: "platform" }
packages { pf: "platform" }
imports []
provides [ main ] to base
provides [ main ] to pf
greeting =
hi = "Hello"

View file

@ -1,6 +1,6 @@
app "hello-world"
packages { base: "platform" }
packages { pf: "platform" }
imports []
provides [ main ] to base
provides [ main ] to pf
main = "Hello, World!\n"

View file

@ -1,7 +1,7 @@
app "hello-world"
packages { base: "platform" }
packages { pf: "platform" }
imports []
provides [ main ] to base
provides [ main ] to pf
greeting =
hi = "Hello"

View file

@ -1,7 +1,7 @@
app "quicksort"
packages { base: "platform" }
packages { pf: "platform" }
imports []
provides [ quicksort ] to base
provides [ quicksort ] to pf
quicksort = \originalList ->
n = List.len originalList

View file

@ -6048,9 +6048,9 @@ I need all branches in an `if` to have the same type!
indoc!(
r#"
app "test-base64"
packages { base: "platform" }
imports [base.Task, Base64 ]
provides [ main, @Foo ] to base
packages { pf: "platform" }
imports [pf.Task, Base64 ]
provides [ main, @Foo ] to pf
"#
),
indoc!(
@ -6059,8 +6059,8 @@ I need all branches in an `if` to have the same type!
I am partway through parsing a provides list, but I got stuck here:
3 imports [base.Task, Base64 ]
4 provides [ main, @Foo ] to base
3 imports [pf.Task, Base64 ]
4 provides [ main, @Foo ] to pf
^
I was expecting a type name, value name or function name next, like
@ -6116,7 +6116,7 @@ I need all branches in an `if` to have the same type!
r#"
interface Foobar
exposes [ main, @Foo ]
imports [base.Task, Base64 ]
imports [pf.Task, Base64 ]
"#
),
indoc!(
@ -6144,7 +6144,7 @@ I need all branches in an `if` to have the same type!
r#"
interface foobar
exposes [ main, @Foo ]
imports [base.Task, Base64 ]
imports [pf.Task, Base64 ]
"#
),
indoc!(
@ -6170,7 +6170,7 @@ I need all branches in an `if` to have the same type!
r#"
app foobar
exposes [ main, @Foo ]
imports [base.Task, Base64 ]
imports [pf.Task, Base64 ]
"#
),
indoc!(