Merge remote-tracking branch 'origin/trunk' into specialize-separately

This commit is contained in:
Richard Feldman 2020-06-13 20:59:11 -04:00
commit 245a9fc951
40 changed files with 2118 additions and 1621 deletions

748
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -54,7 +54,7 @@ clap = { git = "https://github.com/rtfeldman/clap", branch = "master" }
im = "14" # im and im-rc should always have the same version! im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1.0" inlinable_string = "0.1"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", "process", "io-driver"] } tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", "process", "io-driver"] }
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything. # NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything.
# #

View file

@ -331,6 +331,18 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// Float module // Float module
// isGt or (>) : Num a, Num a -> Bool
add_type(
Symbol::FLOAT_GT,
SolvedType::Func(vec![float_type(), float_type()], Box::new(bool_type())),
);
// eq or (==) : Num a, Num a -> Bool
add_type(
Symbol::FLOAT_EQ,
SolvedType::Func(vec![float_type(), float_type()], Box::new(bool_type())),
);
// div : Float, Float -> Float // div : Float, Float -> Float
add_type( add_type(
Symbol::FLOAT_DIV, Symbol::FLOAT_DIV,
@ -361,6 +373,24 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
SolvedType::Func(vec![float_type()], Box::new(float_type())), SolvedType::Func(vec![float_type()], Box::new(float_type())),
); );
// sin : Float -> Float
add_type(
Symbol::FLOAT_SIN,
SolvedType::Func(vec![float_type()], Box::new(float_type())),
);
// cos : Float -> Float
add_type(
Symbol::FLOAT_COS,
SolvedType::Func(vec![float_type()], Box::new(float_type())),
);
// tan : Float -> Float
add_type(
Symbol::FLOAT_TAN,
SolvedType::Func(vec![float_type()], Box::new(float_type())),
);
// highest : Float // highest : Float
add_type(Symbol::FLOAT_HIGHEST, float_type()); add_type(Symbol::FLOAT_HIGHEST, float_type());
@ -477,6 +507,12 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// single : a -> List a
add_type(
Symbol::LIST_SINGLE,
SolvedType::Func(vec![flex(TVAR1)], Box::new(list_type(flex(TVAR1)))),
);
// len : List * -> Int // len : List * -> Int
add_type( add_type(
Symbol::LIST_LEN, Symbol::LIST_LEN,

View file

@ -415,6 +415,18 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// Float module // Float module
// isGt or (>) : Num a, Num a -> Bool
add_type(
Symbol::FLOAT_GT,
unique_function(vec![float_type(UVAR1), float_type(UVAR2)], bool_type(UVAR3)),
);
// eq or (==) : Num a, Num a -> Bool
add_type(
Symbol::FLOAT_EQ,
unique_function(vec![float_type(UVAR1), float_type(UVAR2)], bool_type(UVAR3)),
);
// div : Float, Float -> Float // div : Float, Float -> Float
add_type( add_type(
Symbol::FLOAT_DIV, Symbol::FLOAT_DIV,
@ -451,6 +463,24 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
unique_function(vec![float_type(UVAR1)], float_type(UVAR2)), unique_function(vec![float_type(UVAR1)], float_type(UVAR2)),
); );
// sin : Float -> Float
add_type(
Symbol::FLOAT_SIN,
unique_function(vec![float_type(UVAR1)], float_type(UVAR2)),
);
// cos : Float -> Float
add_type(
Symbol::FLOAT_COS,
unique_function(vec![float_type(UVAR1)], float_type(UVAR2)),
);
// tan : Float -> Float
add_type(
Symbol::FLOAT_TAN,
unique_function(vec![float_type(UVAR1)], float_type(UVAR2)),
);
// highest : Float // highest : Float
add_type(Symbol::FLOAT_HIGHEST, float_type(UVAR1)); add_type(Symbol::FLOAT_HIGHEST, float_type(UVAR1));
@ -563,6 +593,28 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
) )
}); });
add_type(Symbol::LIST_SINGLE, {
let u = UVAR1;
let v = UVAR2;
let star = UVAR4;
let a = TVAR1;
unique_function(
vec![SolvedType::Apply(
Symbol::ATTR_ATTR,
vec![disjunction(u, vec![v]), flex(a)],
)],
SolvedType::Apply(
Symbol::ATTR_ATTR,
vec![
boolean(star),
SolvedType::Apply(Symbol::LIST_LIST, vec![attr_type(u, a)]),
],
),
)
});
// push : Attr (w | u | v) (List (Attr u a)) // push : Attr (w | u | v) (List (Attr u a))
// , Attr (u | v) a // , Attr (u | v) a
// -> Attr * (List (Attr u a)) // -> Attr * (List (Attr u a))

View file

@ -16,7 +16,7 @@ ven_graph = { path = "../../vendor/pathfinding" }
im = "14" # im and im-rc should always have the same version! im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1.0" inlinable_string = "0.1"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1" pretty_assertions = "0.5.1"

View file

@ -1,6 +1,5 @@
use crate::def::Def;
use crate::expr::{Expr, Recursive}; use crate::expr::{Expr, Recursive};
use roc_collections::all::{MutMap, SendMap}; use roc_collections::all::MutMap;
use roc_module::ident::TagName; use roc_module::ident::TagName;
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::operator::CalledVia; use roc_module::operator::CalledVia;
@ -25,7 +24,7 @@ use roc_types::subs::{VarStore, Variable};
/// delegates to the compiler-internal List.getUnsafe function to do the actual /// delegates to the compiler-internal List.getUnsafe function to do the actual
/// lookup (if the bounds check passed). That internal function is hardcoded in code gen, /// lookup (if the bounds check passed). That internal function is hardcoded in code gen,
/// which works fine because it doesn't involve any open tag unions. /// which works fine because it doesn't involve any open tag unions.
pub fn builtin_defs(var_store: &VarStore) -> MutMap<Symbol, Def> { pub fn builtin_defs(var_store: &VarStore) -> MutMap<Symbol, Expr> {
mut_map! { mut_map! {
Symbol::LIST_LEN => list_len(var_store), Symbol::LIST_LEN => list_len(var_store),
Symbol::LIST_GET => list_get(var_store), Symbol::LIST_GET => list_get(var_store),
@ -34,12 +33,151 @@ pub fn builtin_defs(var_store: &VarStore) -> MutMap<Symbol, Def> {
Symbol::INT_ABS => int_abs(var_store), Symbol::INT_ABS => int_abs(var_store),
Symbol::INT_REM => int_rem(var_store), Symbol::INT_REM => int_rem(var_store),
Symbol::INT_IS_ODD => int_is_odd(var_store), Symbol::INT_IS_ODD => int_is_odd(var_store),
Symbol::INT_IS_EVEN => int_is_even(var_store) Symbol::INT_IS_EVEN => int_is_even(var_store),
Symbol::INT_IS_ZERO => int_is_zero(var_store),
Symbol::INT_IS_POSITIVE => int_is_positive(var_store),
Symbol::INT_IS_NEGATIVE => int_is_negative(var_store),
Symbol::FLOAT_IS_POSITIVE => float_is_positive(var_store),
Symbol::FLOAT_IS_NEGATIVE => float_is_negative(var_store),
Symbol::FLOAT_IS_ZERO => float_is_zero(var_store),
Symbol::FLOAT_TAN => float_tan(var_store),
} }
} }
/// Float.tan : Float -> Float
fn float_tan(var_store: &VarStore) -> Expr {
use crate::expr::Expr::*;
defn(
Symbol::FLOAT_TAN,
vec![Symbol::FLOAT_TAN_ARG],
var_store,
call(
Symbol::FLOAT_DIV,
vec![
call(
Symbol::FLOAT_SIN,
vec![Var(Symbol::FLOAT_TAN_ARG)],
var_store,
),
call(
Symbol::FLOAT_COS,
vec![Var(Symbol::FLOAT_TAN_ARG)],
var_store,
),
],
var_store,
),
)
}
/// Float.isZero : Float -> Bool
fn float_is_zero(var_store: &VarStore) -> Expr {
use crate::expr::Expr::*;
defn(
Symbol::FLOAT_IS_ZERO,
vec![Symbol::FLOAT_IS_ZERO_ARG],
var_store,
call(
Symbol::FLOAT_EQ,
vec![
Float(var_store.fresh(), 0.0),
Var(Symbol::FLOAT_IS_ZERO_ARG),
],
var_store,
),
)
}
/// Float.isNegative : Float -> Bool
fn float_is_negative(var_store: &VarStore) -> Expr {
use crate::expr::Expr::*;
defn(
Symbol::FLOAT_IS_NEGATIVE,
vec![Symbol::FLOAT_IS_NEGATIVE_ARG],
var_store,
call(
Symbol::FLOAT_GT,
vec![
Float(var_store.fresh(), 0.0),
Var(Symbol::FLOAT_IS_NEGATIVE_ARG),
],
var_store,
),
)
}
/// Float.isPositive : Float -> Bool
fn float_is_positive(var_store: &VarStore) -> Expr {
use crate::expr::Expr::*;
defn(
Symbol::FLOAT_IS_POSITIVE,
vec![Symbol::FLOAT_IS_POSITIVE_ARG],
var_store,
call(
Symbol::FLOAT_GT,
vec![
Var(Symbol::FLOAT_IS_POSITIVE_ARG),
Float(var_store.fresh(), 0.0),
],
var_store,
),
)
}
/// Int.isNegative : Int -> Bool
fn int_is_negative(var_store: &VarStore) -> Expr {
use crate::expr::Expr::*;
defn(
Symbol::INT_IS_NEGATIVE,
vec![Symbol::INT_IS_NEGATIVE_ARG],
var_store,
call(
Symbol::NUM_LT,
vec![Var(Symbol::INT_IS_NEGATIVE_ARG), Int(var_store.fresh(), 0)],
var_store,
),
)
}
/// Int.isPositive : Int -> Bool
fn int_is_positive(var_store: &VarStore) -> Expr {
use crate::expr::Expr::*;
defn(
Symbol::INT_IS_POSITIVE,
vec![Symbol::INT_IS_POSITIVE_ARG],
var_store,
call(
Symbol::NUM_GT,
vec![Var(Symbol::INT_IS_POSITIVE_ARG), Int(var_store.fresh(), 0)],
var_store,
),
)
}
/// Int.isZero : Int -> Bool
fn int_is_zero(var_store: &VarStore) -> Expr {
use crate::expr::Expr::*;
defn(
Symbol::INT_IS_ZERO,
vec![Symbol::INT_IS_ZERO_ARG],
var_store,
call(
Symbol::INT_EQ_I64,
vec![Var(Symbol::INT_IS_ZERO_ARG), Int(var_store.fresh(), 0)],
var_store,
),
)
}
/// Int.isOdd : Int -> Bool /// Int.isOdd : Int -> Bool
fn int_is_odd(var_store: &VarStore) -> Def { fn int_is_odd(var_store: &VarStore) -> Expr {
use crate::expr::Expr::*; use crate::expr::Expr::*;
defn( defn(
@ -62,7 +200,7 @@ fn int_is_odd(var_store: &VarStore) -> Def {
} }
/// Int.isEven : Int -> Bool /// Int.isEven : Int -> Bool
fn int_is_even(var_store: &VarStore) -> Def { fn int_is_even(var_store: &VarStore) -> Expr {
use crate::expr::Expr::*; use crate::expr::Expr::*;
defn( defn(
@ -85,7 +223,7 @@ fn int_is_even(var_store: &VarStore) -> Def {
} }
/// List.len : List * -> Int /// List.len : List * -> Int
fn list_len(var_store: &VarStore) -> Def { fn list_len(var_store: &VarStore) -> Expr {
use crate::expr::Expr::*; use crate::expr::Expr::*;
// Polymorphic wrapper around LowLevel::ListLen // Polymorphic wrapper around LowLevel::ListLen
@ -100,7 +238,7 @@ fn list_len(var_store: &VarStore) -> Def {
} }
/// List.get : List elem, Int -> Result elem [ OutOfBounds ]* /// List.get : List elem, Int -> Result elem [ OutOfBounds ]*
fn list_get(var_store: &VarStore) -> Def { fn list_get(var_store: &VarStore) -> Expr {
use crate::expr::Expr::*; use crate::expr::Expr::*;
defn( defn(
@ -169,7 +307,7 @@ fn list_get(var_store: &VarStore) -> Def {
} }
/// Int.rem : Int, Int -> Int /// Int.rem : Int, Int -> Int
fn int_rem(var_store: &VarStore) -> Def { fn int_rem(var_store: &VarStore) -> Expr {
use crate::expr::Expr::*; use crate::expr::Expr::*;
defn( defn(
@ -216,7 +354,7 @@ fn int_rem(var_store: &VarStore) -> Def {
} }
/// Int.abs : Int -> Int /// Int.abs : Int -> Int
fn int_abs(var_store: &VarStore) -> Def { fn int_abs(var_store: &VarStore) -> Expr {
use crate::expr::Expr::*; use crate::expr::Expr::*;
defn( defn(
@ -253,7 +391,7 @@ fn int_abs(var_store: &VarStore) -> Def {
} }
/// Int.div : Int, Int -> Result Int [ DivByZero ]* /// Int.div : Int, Int -> Result Int [ DivByZero ]*
fn int_div(var_store: &VarStore) -> Def { fn int_div(var_store: &VarStore) -> Expr {
use crate::expr::Expr::*; use crate::expr::Expr::*;
defn( defn(
@ -312,7 +450,7 @@ fn int_div(var_store: &VarStore) -> Def {
} }
/// List.first : List elem -> Result elem [ ListWasEmpty ]* /// List.first : List elem -> Result elem [ ListWasEmpty ]*
fn list_first(var_store: &VarStore) -> Def { fn list_first(var_store: &VarStore) -> Expr {
use crate::expr::Expr::*; use crate::expr::Expr::*;
defn( defn(
@ -404,7 +542,7 @@ fn call(symbol: Symbol, args: Vec<Expr>, var_store: &VarStore) -> Expr {
} }
#[inline(always)] #[inline(always)]
fn defn(fn_name: Symbol, args: Vec<Symbol>, var_store: &VarStore, body: Expr) -> Def { fn defn(fn_name: Symbol, args: Vec<Symbol>, var_store: &VarStore, body: Expr) -> Expr {
use crate::expr::Expr::*; use crate::expr::Expr::*;
use crate::pattern::Pattern::*; use crate::pattern::Pattern::*;
@ -413,19 +551,11 @@ fn defn(fn_name: Symbol, args: Vec<Symbol>, var_store: &VarStore, body: Expr) ->
.map(|symbol| (var_store.fresh(), no_region(Identifier(symbol)))) .map(|symbol| (var_store.fresh(), no_region(Identifier(symbol))))
.collect(); .collect();
let expr = Closure( Closure(
var_store.fresh(), var_store.fresh(),
fn_name, fn_name,
Recursive::NotRecursive, Recursive::NotRecursive,
closure_args, closure_args,
Box::new((no_region(body), var_store.fresh())), Box::new((no_region(body), var_store.fresh())),
); )
Def {
loc_pattern: no_region(Identifier(fn_name)),
loc_expr: no_region(expr),
expr_var: var_store.fresh(),
pattern_vars: SendMap::default(),
annotation: None,
}
} }

View file

@ -360,8 +360,8 @@ pub fn sort_can_defs(
let mut defined_symbols_set: ImSet<Symbol> = ImSet::default(); let mut defined_symbols_set: ImSet<Symbol> = ImSet::default();
for symbol in can_defs_by_symbol.keys().into_iter() { for symbol in can_defs_by_symbol.keys().into_iter() {
defined_symbols.push(symbol.clone()); defined_symbols.push(*symbol);
defined_symbols_set.insert(symbol.clone()); defined_symbols_set.insert(*symbol);
} }
// Use topological sort to reorder the defs based on their dependencies to one another. // Use topological sort to reorder the defs based on their dependencies to one another.
@ -690,7 +690,7 @@ fn pattern_to_vars_by_symbol(
use Pattern::*; use Pattern::*;
match pattern { match pattern {
Identifier(symbol) => { Identifier(symbol) => {
vars_by_symbol.insert(symbol.clone(), expr_var); vars_by_symbol.insert(*symbol, expr_var);
} }
AppliedTag { arguments, .. } => { AppliedTag { arguments, .. } => {
@ -701,7 +701,7 @@ fn pattern_to_vars_by_symbol(
RecordDestructure { destructs, .. } => { RecordDestructure { destructs, .. } => {
for destruct in destructs { for destruct in destructs {
vars_by_symbol.insert(destruct.value.symbol.clone(), destruct.value.var); vars_by_symbol.insert(destruct.value.symbol, destruct.value.var);
} }
} }
@ -949,7 +949,7 @@ fn canonicalize_pending_def<'a>(
) = ( ) = (
&loc_pattern.value, &loc_pattern.value,
&loc_can_pattern.value, &loc_can_pattern.value,
&loc_can_expr.value.clone(), &loc_can_expr.value,
) { ) {
is_closure = true; is_closure = true;
@ -966,7 +966,7 @@ fn canonicalize_pending_def<'a>(
// closures don't have a name, and therefore pick a fresh symbol. But in this // closures don't have a name, and therefore pick a fresh symbol. But in this
// case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...` // case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...`
// and we want to reference it by that name. // and we want to reference it by that name.
env.closures.insert(defined_symbol.clone(), references); env.closures.insert(*defined_symbol, references);
// The closure is self tail recursive iff it tail calls itself (by defined name). // The closure is self tail recursive iff it tail calls itself (by defined name).
let is_recursive = match can_output.tail_call { let is_recursive = match can_output.tail_call {
@ -977,7 +977,7 @@ fn canonicalize_pending_def<'a>(
// Recursion doesn't count as referencing. (If it did, all recursive functions // Recursion doesn't count as referencing. (If it did, all recursive functions
// would result in circular def errors!) // would result in circular def errors!)
refs_by_symbol refs_by_symbol
.entry(defined_symbol.clone()) .entry(*defined_symbol)
.and_modify(|(_, refs)| { .and_modify(|(_, refs)| {
refs.lookups = refs.lookups.without(defined_symbol); refs.lookups = refs.lookups.without(defined_symbol);
}); });
@ -1010,7 +1010,7 @@ fn canonicalize_pending_def<'a>(
}; };
refs_by_symbol.insert( refs_by_symbol.insert(
symbol.clone(), *symbol,
( (
Located { Located {
value: ident.clone(), value: ident.clone(),
@ -1057,7 +1057,7 @@ fn canonicalize_pending_def<'a>(
env.tailcallable_symbol = Some(*defined_symbol); env.tailcallable_symbol = Some(*defined_symbol);
// TODO isn't types_by_symbol enough? Do we need vars_by_symbol too? // TODO isn't types_by_symbol enough? Do we need vars_by_symbol too?
vars_by_symbol.insert(defined_symbol.clone(), expr_var); vars_by_symbol.insert(*defined_symbol, expr_var);
}; };
let (mut loc_can_expr, can_output) = let (mut loc_can_expr, can_output) =
@ -1082,7 +1082,7 @@ fn canonicalize_pending_def<'a>(
) = ( ) = (
&loc_pattern.value, &loc_pattern.value,
&loc_can_pattern.value, &loc_can_pattern.value,
&loc_can_expr.value.clone(), &loc_can_expr.value,
) { ) {
is_closure = true; is_closure = true;
@ -1099,7 +1099,7 @@ fn canonicalize_pending_def<'a>(
// closures don't have a name, and therefore pick a fresh symbol. But in this // closures don't have a name, and therefore pick a fresh symbol. But in this
// case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...` // case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...`
// and we want to reference it by that name. // and we want to reference it by that name.
env.closures.insert(defined_symbol.clone(), references); env.closures.insert(*defined_symbol, references);
// The closure is self tail recursive iff it tail calls itself (by defined name). // The closure is self tail recursive iff it tail calls itself (by defined name).
let is_recursive = match can_output.tail_call { let is_recursive = match can_output.tail_call {
@ -1110,7 +1110,7 @@ fn canonicalize_pending_def<'a>(
// Recursion doesn't count as referencing. (If it did, all recursive functions // Recursion doesn't count as referencing. (If it did, all recursive functions
// would result in circular def errors!) // would result in circular def errors!)
refs_by_symbol refs_by_symbol
.entry(defined_symbol.clone()) .entry(*defined_symbol)
.and_modify(|(_, refs)| { .and_modify(|(_, refs)| {
refs.lookups = refs.lookups.without(defined_symbol); refs.lookups = refs.lookups.without(defined_symbol);
}); });
@ -1147,7 +1147,7 @@ fn canonicalize_pending_def<'a>(
}); });
refs_by_symbol.insert( refs_by_symbol.insert(
symbol.clone(), symbol,
( (
Located { Located {
value: ident.clone().into(), value: ident.clone().into(),
@ -1265,7 +1265,7 @@ fn closure_recursivity(symbol: Symbol, closures: &MutMap<Symbol, References>) ->
if let Some(references) = closures.get(&symbol) { if let Some(references) = closures.get(&symbol) {
for v in &references.calls { for v in &references.calls {
stack.push(v.clone()); stack.push(*v);
} }
// while there are symbols left to visit // while there are symbols left to visit
@ -1281,7 +1281,7 @@ fn closure_recursivity(symbol: Symbol, closures: &MutMap<Symbol, References>) ->
if let Some(nested_references) = closures.get(&nested_symbol) { if let Some(nested_references) = closures.get(&nested_symbol) {
// add its called to the stack // add its called to the stack
for v in &nested_references.calls { for v in &nested_references.calls {
stack.push(v.clone()); stack.push(*v);
} }
} }
visited.insert(nested_symbol); visited.insert(nested_symbol);

View file

@ -452,7 +452,7 @@ pub fn canonicalize_expr<'a>(
} }
} }
env.register_closure(symbol.clone(), output.references.clone()); env.register_closure(symbol, output.references.clone());
( (
Closure( Closure(
@ -820,7 +820,7 @@ where
answer = answer.union(other_refs); answer = answer.union(other_refs);
} }
answer.lookups.insert(local.clone()); answer.lookups.insert(*local);
} }
for call in refs.calls.iter() { for call in refs.calls.iter() {
@ -830,7 +830,7 @@ where
answer = answer.union(other_refs); answer = answer.union(other_refs);
} }
answer.calls.insert(call.clone()); answer.calls.insert(*call);
} }
answer answer
@ -862,7 +862,7 @@ where
answer = answer.union(other_refs); answer = answer.union(other_refs);
} }
answer.lookups.insert(closed_over_local.clone()); answer.lookups.insert(*closed_over_local);
} }
for call in references.calls.iter() { for call in references.calls.iter() {

View file

@ -57,7 +57,7 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
match pattern { match pattern {
Identifier(symbol) => { Identifier(symbol) => {
symbols.push(symbol.clone()); symbols.push(*symbol);
} }
AppliedTag { arguments, .. } => { AppliedTag { arguments, .. } => {
@ -67,7 +67,7 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
} }
RecordDestructure { destructs, .. } => { RecordDestructure { destructs, .. } => {
for destruct in destructs { for destruct in destructs {
symbols.push(destruct.value.symbol.clone()); symbols.push(destruct.value.symbol);
} }
} }

View file

@ -47,7 +47,7 @@ fn headers_from_annotation_help(
) -> bool { ) -> bool {
match pattern { match pattern {
Identifier(symbol) => { Identifier(symbol) => {
headers.insert(symbol.clone(), annotation.clone()); headers.insert(*symbol, annotation.clone());
true true
} }
Underscore Underscore
@ -64,7 +64,7 @@ fn headers_from_annotation_help(
// NOTE ignores the .guard field. // NOTE ignores the .guard field.
if let Some(field_type) = fields.get(&destruct.value.label) { if let Some(field_type) = fields.get(&destruct.value.label) {
headers.insert( headers.insert(
destruct.value.symbol.clone(), destruct.value.symbol,
Located::at(annotation.region, field_type.clone()), Located::at(annotation.region, field_type.clone()),
); );
} else { } else {
@ -123,7 +123,7 @@ pub fn constrain_pattern(
Identifier(symbol) => { Identifier(symbol) => {
state.headers.insert( state.headers.insert(
symbol.clone(), *symbol,
Located { Located {
region, region,
value: expected.get_type(), value: expected.get_type(),
@ -197,7 +197,7 @@ pub fn constrain_pattern(
if !state.headers.contains_key(&symbol) { if !state.headers.contains_key(&symbol) {
state state
.headers .headers
.insert(symbol.clone(), Located::at(region, pat_type.clone())); .insert(*symbol, Located::at(region, pat_type.clone()));
} }
field_types.insert(label.clone(), pat_type.clone()); field_types.insert(label.clone(), pat_type.clone());

View file

@ -148,7 +148,7 @@ fn constrain_pattern(
match &pattern.value { match &pattern.value {
Identifier(symbol) => { Identifier(symbol) => {
state.headers.insert( state.headers.insert(
symbol.clone(), *symbol,
Located { Located {
region: pattern.region, region: pattern.region,
value: expected.get_type(), value: expected.get_type(),
@ -223,10 +223,9 @@ fn constrain_pattern(
let expected = PExpected::NoExpectation(pat_type.clone()); let expected = PExpected::NoExpectation(pat_type.clone());
if !state.headers.contains_key(&symbol) { if !state.headers.contains_key(&symbol) {
state.headers.insert( state
symbol.clone(), .headers
Located::at(pattern.region, pat_type.clone()), .insert(*symbol, Located::at(pattern.region, pat_type.clone()));
);
} }
field_types.insert(label.clone(), pat_type.clone()); field_types.insert(label.clone(), pat_type.clone());
@ -1396,7 +1395,7 @@ fn constrain_var(
Lookup(symbol_for_lookup, expected, region) Lookup(symbol_for_lookup, expected, region)
} }
Some(Usage::Access(_, _, _)) | Some(Usage::Update(_, _, _)) => { Some(Usage::Access(_, _, _)) | Some(Usage::Update(_, _, _)) => {
applied_usage_constraint.insert(symbol_for_lookup.clone()); applied_usage_constraint.insert(symbol_for_lookup);
let mut variables = Vec::new(); let mut variables = Vec::new();
let (free, rest, inner_type) = let (free, rest, inner_type) =

View file

@ -15,7 +15,7 @@ roc_types = { path = "../types" }
im = "14" # im and im-rc should always have the same version! im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1.0" inlinable_string = "0.1"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1" pretty_assertions = "0.5.1"

View file

@ -20,7 +20,7 @@ roc_mono = { path = "../mono" }
im = "14" # im and im-rc should always have the same version! im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1.0" inlinable_string = "0.1"
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything. # NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything.
# #
# The reason for this fork is that the way Inkwell is designed, you have to use # The reason for this fork is that the way Inkwell is designed, you have to use

View file

@ -88,11 +88,25 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
LLVM_FABS_F64, LLVM_FABS_F64,
f64_type.fn_type(&[f64_type.into()], false), f64_type.fn_type(&[f64_type.into()], false),
); );
add_intrinsic(
module,
LLVM_SIN_F64,
f64_type.fn_type(&[f64_type.into()], false),
);
add_intrinsic(
module,
LLVM_COS_F64,
f64_type.fn_type(&[f64_type.into()], false),
);
} }
static LLVM_SQRT_F64: &str = "llvm.sqrt.f64"; static LLVM_SQRT_F64: &str = "llvm.sqrt.f64";
static LLVM_LROUND_I64_F64: &str = "llvm.lround.i64.f64"; static LLVM_LROUND_I64_F64: &str = "llvm.lround.i64.f64";
static LLVM_FABS_F64: &str = "llvm.fabs.f64"; static LLVM_FABS_F64: &str = "llvm.fabs.f64";
static LLVM_SIN_F64: &str = "llvm.sin.f64";
static LLVM_COS_F64: &str = "llvm.cos.f64";
fn add_intrinsic<'ctx>( fn add_intrinsic<'ctx>(
module: &Module<'ctx>, module: &Module<'ctx>,
@ -422,7 +436,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
if elems.is_empty() { if elems.is_empty() {
let struct_type = collection(ctx, env.ptr_bytes); let struct_type = collection(ctx, env.ptr_bytes);
// THe pointer should be null (aka zero) and the length should be zero, // The pointer should be null (aka zero) and the length should be zero,
// so the whole struct should be a const_zero // so the whole struct should be a const_zero
BasicValueEnum::StructValue(struct_type.const_zero()) BasicValueEnum::StructValue(struct_type.const_zero())
} else { } else {
@ -1203,6 +1217,8 @@ fn call_with_args<'a, 'ctx, 'env>(
BasicValueEnum::IntValue(bool_val) BasicValueEnum::IntValue(bool_val)
} }
Symbol::FLOAT_SIN => call_intrinsic(LLVM_SIN_F64, env, args),
Symbol::FLOAT_COS => call_intrinsic(LLVM_COS_F64, env, args),
Symbol::NUM_MUL => { Symbol::NUM_MUL => {
debug_assert!(args.len() == 2); debug_assert!(args.len() == 2);
@ -1396,6 +1412,74 @@ fn call_with_args<'a, 'ctx, 'env>(
Symbol::FLOAT_ROUND => call_intrinsic(LLVM_LROUND_I64_F64, env, args), Symbol::FLOAT_ROUND => call_intrinsic(LLVM_LROUND_I64_F64, env, args),
Symbol::LIST_SET => list_set(parent, args, env, InPlace::Clone), Symbol::LIST_SET => list_set(parent, args, env, InPlace::Clone),
Symbol::LIST_SET_IN_PLACE => list_set(parent, args, env, InPlace::InPlace), Symbol::LIST_SET_IN_PLACE => list_set(parent, args, env, InPlace::InPlace),
Symbol::LIST_SINGLE => {
// List.single : a -> List a
debug_assert!(args.len() == 1);
let (elem, elem_layout) = args[0];
let builder = env.builder;
let ctx = env.context;
let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes);
let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64;
let ptr = {
let bytes_len = elem_bytes;
let len_type = env.ptr_int();
let len = len_type.const_int(bytes_len, false);
env.builder
.build_array_malloc(elem_type, len, "create_list_ptr")
.unwrap()
// TODO check if malloc returned null; if so, runtime error for OOM!
};
// Put the element into the list
let elem_ptr = unsafe {
builder.build_in_bounds_gep(
ptr,
&[ctx.i32_type().const_int(
// 0 as in 0 index of our new list
0 as u64, false,
)],
"index",
)
};
builder.build_store(elem_ptr, elem);
let ptr_bytes = env.ptr_bytes;
let int_type = ptr_int(ctx, ptr_bytes);
let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "list_cast_ptr");
let struct_type = collection(ctx, ptr_bytes);
let len = BasicValueEnum::IntValue(env.ptr_int().const_int(1, false));
let mut struct_val;
// Store the pointer
struct_val = builder
.build_insert_value(
struct_type.get_undef(),
ptr_as_int,
Builtin::WRAPPER_PTR,
"insert_ptr",
)
.unwrap();
// Store the length
struct_val = builder
.build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len")
.unwrap();
//
builder.build_bitcast(
struct_val.into_struct_value(),
collection(ctx, ptr_bytes),
"cast_collection",
)
}
Symbol::INT_DIV_UNSAFE => { Symbol::INT_DIV_UNSAFE => {
debug_assert!(args.len() == 2); debug_assert!(args.len() == 2);

View file

@ -95,6 +95,7 @@ mod gen_builtins {
#[test] #[test]
fn gen_add_f64() { fn gen_add_f64() {
with_larger_debug_stack(|| {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
@ -104,6 +105,7 @@ mod gen_builtins {
6.5, 6.5,
f64 f64
); );
})
} }
#[test] #[test]
@ -147,6 +149,7 @@ mod gen_builtins {
#[test] #[test]
fn gen_add_i64() { fn gen_add_i64() {
with_larger_debug_stack(|| {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
@ -156,6 +159,7 @@ mod gen_builtins {
6, 6,
i64 i64
); );
})
} }
#[test] #[test]
@ -257,6 +261,48 @@ mod gen_builtins {
); );
} }
#[test]
fn gen_is_zero_i64() {
assert_evals_to!("Int.isZero 0", true, bool);
assert_evals_to!("Int.isZero 1", false, bool);
}
#[test]
fn gen_is_positive_i64() {
assert_evals_to!("Int.isPositive 0", false, bool);
assert_evals_to!("Int.isPositive 1", true, bool);
assert_evals_to!("Int.isPositive -5", false, bool);
}
#[test]
fn gen_is_negative_i64() {
assert_evals_to!("Int.isNegative 0", false, bool);
assert_evals_to!("Int.isNegative 3", false, bool);
assert_evals_to!("Int.isNegative -2", true, bool);
}
#[test]
fn gen_is_positive_f64() {
assert_evals_to!("Float.isPositive 0.0", false, bool);
assert_evals_to!("Float.isPositive 4.7", true, bool);
assert_evals_to!("Float.isPositive -8.5", false, bool);
}
#[test]
fn gen_is_negative_f64() {
assert_evals_to!("Float.isNegative 0.0", false, bool);
assert_evals_to!("Float.isNegative 9.9", false, bool);
assert_evals_to!("Float.isNegative -4.4", true, bool);
}
#[test]
fn gen_is_zero_f64() {
assert_evals_to!("Float.isZero 0", true, bool);
assert_evals_to!("Float.isZero 0_0", true, bool);
assert_evals_to!("Float.isZero 0.0", true, bool);
assert_evals_to!("Float.isZero 1", false, bool);
}
#[test] #[test]
fn gen_is_odd() { fn gen_is_odd() {
assert_evals_to!("Int.isOdd 4", false, bool); assert_evals_to!("Int.isOdd 4", false, bool);
@ -269,6 +315,24 @@ mod gen_builtins {
assert_evals_to!("Int.isEven 7", false, bool); assert_evals_to!("Int.isEven 7", false, bool);
} }
#[test]
fn sin() {
assert_evals_to!("Float.sin 0", 0.0, f64);
assert_evals_to!("Float.sin 1.41421356237", 0.9877659459922529, f64);
}
#[test]
fn cos() {
assert_evals_to!("Float.cos 0", 1.0, f64);
assert_evals_to!("Float.cos 3.14159265359", -1.0, f64);
}
#[test]
fn tan() {
assert_evals_to!("Float.tan 0", 0.0, f64);
assert_evals_to!("Float.tan 1", 1.557407724654902, f64);
}
#[test] #[test]
fn lt_i64() { fn lt_i64() {
assert_evals_to!("1 < 2", true, bool); assert_evals_to!("1 < 2", true, bool);
@ -387,6 +451,7 @@ mod gen_builtins {
} }
#[test] #[test]
fn tail_call_elimination() { fn tail_call_elimination() {
with_larger_debug_stack(|| {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
@ -401,6 +466,7 @@ mod gen_builtins {
500000500000, 500000500000,
i64 i64
); );
})
} }
#[test] #[test]
fn int_negate() { fn int_negate() {
@ -423,14 +489,29 @@ mod gen_builtins {
); );
} }
// #[test]
// fn list_push() {
// assert_evals_to!("List.push [] 1", &[1], &'static [i64]);
// }
#[test]
fn list_single() {
assert_evals_to!("List.single 1", &[1], &'static [i64]);
assert_evals_to!("List.single 5.6", &[5.6], &'static [f64]);
}
#[test] #[test]
fn empty_list_len() { fn empty_list_len() {
with_larger_debug_stack(|| {
assert_evals_to!("List.len []", 0, usize); assert_evals_to!("List.len []", 0, usize);
})
} }
#[test] #[test]
fn basic_int_list_len() { fn basic_int_list_len() {
with_larger_debug_stack(|| {
assert_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, usize); assert_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, usize);
})
} }
#[test] #[test]
@ -472,11 +553,14 @@ mod gen_builtins {
#[test] #[test]
fn empty_list_is_empty() { fn empty_list_is_empty() {
with_larger_debug_stack(|| {
assert_evals_to!("List.isEmpty []", true, bool); assert_evals_to!("List.isEmpty []", true, bool);
})
} }
#[test] #[test]
fn first_int_list() { fn first_int_list() {
with_larger_debug_stack(|| {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
@ -488,10 +572,12 @@ mod gen_builtins {
12, 12,
i64 i64
); );
})
} }
#[test] #[test]
fn first_empty_list() { fn first_empty_list() {
with_larger_debug_stack(|| {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
@ -503,6 +589,7 @@ mod gen_builtins {
-1, -1,
i64 i64
); );
})
} }
#[test] #[test]

View file

@ -13,7 +13,7 @@ mod helpers;
#[cfg(test)] #[cfg(test)]
mod gen_primitives { mod gen_primitives {
use crate::helpers::{can_expr, infer_expr, uniq_expr, CanExprOut}; use crate::helpers::{can_expr, infer_expr, uniq_expr, with_larger_debug_stack, CanExprOut};
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::execution_engine::JitFunction; use inkwell::execution_engine::JitFunction;
@ -406,6 +406,7 @@ mod gen_primitives {
#[test] #[test]
fn gen_chained_defs() { fn gen_chained_defs() {
with_larger_debug_stack(|| {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
@ -421,9 +422,11 @@ mod gen_primitives {
1337, 1337,
i64 i64
); );
})
} }
#[test] #[test]
fn gen_nested_defs() { fn gen_nested_defs() {
with_larger_debug_stack(|| {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
@ -461,5 +464,6 @@ mod gen_primitives {
1337, 1337,
i64 i64
); );
})
} }
} }

View file

@ -13,7 +13,7 @@ mod helpers;
#[cfg(test)] #[cfg(test)]
mod gen_tags { mod gen_tags {
use crate::helpers::{can_expr, infer_expr, uniq_expr, CanExprOut}; use crate::helpers::{can_expr, infer_expr, uniq_expr, with_larger_debug_stack, CanExprOut};
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::execution_engine::JitFunction; use inkwell::execution_engine::JitFunction;
@ -213,6 +213,7 @@ mod gen_tags {
#[test] #[test]
fn even_odd() { fn even_odd() {
with_larger_debug_stack(|| {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
@ -234,6 +235,7 @@ mod gen_tags {
true, true,
bool bool
); );
})
} }
#[test] #[test]

View file

@ -6,10 +6,12 @@ pub mod eval;
use self::bumpalo::Bump; use self::bumpalo::Bump;
use roc_builtins::unique::uniq_stdlib; use roc_builtins::unique::uniq_stdlib;
use roc_can::constraint::Constraint; use roc_can::constraint::Constraint;
use roc_can::def::Def;
use roc_can::env::Env; use roc_can::env::Env;
use roc_can::expected::Expected; use roc_can::expected::Expected;
use roc_can::expr::{canonicalize_expr, Expr, Output}; use roc_can::expr::{canonicalize_expr, Expr, Output};
use roc_can::operator; use roc_can::operator;
use roc_can::pattern::Pattern;
use roc_can::scope::Scope; use roc_can::scope::Scope;
use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet}; use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet};
use roc_constrain::expr::constrain_expr; use roc_constrain::expr::constrain_expr;
@ -245,9 +247,23 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
// since we aren't using modules here. // since we aren't using modules here.
let builtin_defs = roc_can::builtins::builtin_defs(&var_store); let builtin_defs = roc_can::builtins::builtin_defs(&var_store);
for def in builtin_defs { for (symbol, expr) in builtin_defs {
if output.references.lookups.contains(&symbol) || output.references.calls.contains(&symbol)
{
with_builtins = Expr::LetNonRec( with_builtins = Expr::LetNonRec(
Box::new(def), Box::new(Def {
loc_pattern: Located {
region: Region::zero(),
value: Pattern::Identifier(symbol),
},
loc_expr: Located {
region: Region::zero(),
value: expr,
},
expr_var: var_store.fresh(),
pattern_vars: SendMap::default(),
annotation: None,
}),
Box::new(Located { Box::new(Located {
region: Region::zero(), region: Region::zero(),
value: with_builtins, value: with_builtins,
@ -256,6 +272,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
SendMap::default(), SendMap::default(),
); );
} }
}
let loc_expr = Located { let loc_expr = Located {
region: loc_expr.region, region: loc_expr.region,

View file

@ -18,7 +18,7 @@ roc_unify = { path = "../unify" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1.0" inlinable_string = "0.1"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] } tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
[dev-dependencies] [dev-dependencies]

View file

@ -9,7 +9,7 @@ license = "Apache-2.0"
roc_region = { path = "../region" } roc_region = { path = "../region" }
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1.0" inlinable_string = "0.1"
lazy_static = "1.4" lazy_static = "1.4"
[dev-dependencies] [dev-dependencies]

View file

@ -623,6 +623,12 @@ define_builtins! {
29 INT_IS_ODD_ARG: "isOdd#arg" 29 INT_IS_ODD_ARG: "isOdd#arg"
30 INT_IS_EVEN: "isEven" 30 INT_IS_EVEN: "isEven"
31 INT_IS_EVEN_ARG: "isEven#arg" 31 INT_IS_EVEN_ARG: "isEven#arg"
32 INT_IS_ZERO: "isZero"
33 INT_IS_ZERO_ARG: "isZero#arg"
34 INT_IS_POSITIVE: "isPositive"
35 INT_IS_POSITIVE_ARG: "isPositive#arg"
36 INT_IS_NEGATIVE: "isNegative"
37 INT_IS_NEGATIVE_ARG: "isNegative#arg"
} }
3 FLOAT: "Float" => { 3 FLOAT: "Float" => {
0 FLOAT_FLOAT: "Float" imported // the Float.Float type alias 0 FLOAT_FLOAT: "Float" imported // the Float.Float type alias
@ -635,13 +641,23 @@ define_builtins! {
7 FLOAT_LOWEST: "lowest" 7 FLOAT_LOWEST: "lowest"
8 FLOAT_ADD: "#add" 8 FLOAT_ADD: "#add"
9 FLOAT_SUB: "#sub" 9 FLOAT_SUB: "#sub"
10 FLOAT_EQ: "#eq" 10 FLOAT_EQ: "eq"
11 FLOAT_ROUND: "round" 11 FLOAT_ROUND: "round"
12 FLOAT_LT: "#lt" 12 FLOAT_LT: "#lt"
13 FLOAT_LTE: "#lte" 13 FLOAT_LTE: "#lte"
14 FLOAT_GT: "#gt" 14 FLOAT_GT: "gt"
15 FLOAT_GTE: "#gte" 15 FLOAT_GTE: "#gte"
16 FLOAT_ABS: "abs" 16 FLOAT_ABS: "abs"
17 FLOAT_IS_POSITIVE: "isPositive"
18 FLOAT_IS_POSITIVE_ARG: "isPositive#arg"
19 FLOAT_IS_NEGATIVE: "isNegative"
20 FLOAT_IS_NEGATIVE_ARG: "isNegative#arg"
21 FLOAT_IS_ZERO: "isZero"
22 FLOAT_IS_ZERO_ARG: "isZero#arg"
23 FLOAT_SIN: "sin"
24 FLOAT_COS: "cos"
25 FLOAT_TAN: "tan"
26 FLOAT_TAN_ARG: "tan#arg"
} }
4 BOOL: "Bool" => { 4 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
@ -676,6 +692,7 @@ define_builtins! {
15 LIST_CONCAT: "concat" 15 LIST_CONCAT: "concat"
16 LIST_FIRST: "first" 16 LIST_FIRST: "first"
17 LIST_FIRST_ARG: "first#list" 17 LIST_FIRST_ARG: "first#list"
18 LIST_SINGLE: "single"
} }
7 RESULT: "Result" => { 7 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias 0 RESULT_RESULT: "Result" imported // the Result.Result type alias

View file

@ -10,7 +10,7 @@ roc_collections = { path = "../collections" }
roc_region = { path = "../region" } roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1.0" inlinable_string = "0.1"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1" pretty_assertions = "0.5.1"

View file

@ -10,7 +10,7 @@ roc_collections = { path = "../collections" }
roc_region = { path = "../region" } roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
inlinable_string = "0.1.0" inlinable_string = "0.1"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1" pretty_assertions = "0.5.1"

View file

@ -17,7 +17,7 @@ roc_can = { path = "../can" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
ven_pretty = { path = "../../vendor/pretty" } ven_pretty = { path = "../../vendor/pretty" }
inlinable_string = "0.1.0" inlinable_string = "0.1"
im = "14" # im and im-rc should always have the same version! im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version!
distance = "0.4.0" distance = "0.4.0"

View file

@ -280,7 +280,7 @@ fn pretty_runtime_error<'b>(
if idents.is_empty() { if idents.is_empty() {
alloc alloc
.reflow("The ") .reflow("The ")
.append(alloc.ident(first.value.clone())) .append(alloc.ident(first.value))
.append(alloc.reflow( .append(alloc.reflow(
" value is defined directly in terms of itself, causing an infinite loop.", " value is defined directly in terms of itself, causing an infinite loop.",
)) ))

View file

@ -333,7 +333,7 @@ fn solve(
let var = type_to_var(subs, rank, pools, cached_aliases, &loc_type.value); let var = type_to_var(subs, rank, pools, cached_aliases, &loc_type.value);
local_def_vars.insert( local_def_vars.insert(
symbol.clone(), *symbol,
Located { Located {
value: var, value: var,
region: loc_type.region, region: loc_type.region,
@ -344,7 +344,7 @@ fn solve(
let mut new_env = env.clone(); let mut new_env = env.clone();
for (symbol, loc_var) in local_def_vars.iter() { for (symbol, loc_var) in local_def_vars.iter() {
if !new_env.vars_by_symbol.contains_key(&symbol) { if !new_env.vars_by_symbol.contains_key(&symbol) {
new_env.vars_by_symbol.insert(symbol.clone(), loc_var.value); new_env.vars_by_symbol.insert(*symbol, loc_var.value);
} }
} }
@ -398,7 +398,7 @@ fn solve(
type_to_var(subs, next_rank, next_pools, cached_aliases, &def_type); type_to_var(subs, next_rank, next_pools, cached_aliases, &def_type);
local_def_vars.insert( local_def_vars.insert(
symbol.clone(), *symbol,
Located { Located {
value: var, value: var,
region: loc_type.region, region: loc_type.region,
@ -469,7 +469,7 @@ fn solve(
for (symbol, loc_var) in local_def_vars.iter() { for (symbol, loc_var) in local_def_vars.iter() {
if !new_env.vars_by_symbol.contains_key(&symbol) { if !new_env.vars_by_symbol.contains_key(&symbol) {
new_env.vars_by_symbol.insert(symbol.clone(), loc_var.value); new_env.vars_by_symbol.insert(*symbol, loc_var.value);
} }
} }

View file

@ -116,6 +116,7 @@ mod test_uniq_solve {
#[test] #[test]
fn empty_list_literal() { fn empty_list_literal() {
with_larger_debug_stack(|| {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
@ -124,6 +125,7 @@ mod test_uniq_solve {
), ),
"Attr * (List *)", "Attr * (List *)",
); );
})
} }
#[test] #[test]

View file

@ -12,7 +12,7 @@ roc_module = { path = "../module" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" } roc_problem = { path = "../problem" }
ven_ena = { path = "../../vendor/ena" } ven_ena = { path = "../../vendor/ena" }
inlinable_string = "0.1.0" inlinable_string = "0.1"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1" pretty_assertions = "0.5.1"

View file

@ -74,7 +74,7 @@ impl Bool {
for atom in &self.1 { for atom in &self.1 {
if let Variable(v) = atom { if let Variable(v) = atom {
result.insert(v.clone()); result.insert(*v);
} }
} }
@ -152,7 +152,7 @@ impl Bool {
if let Variable(v) = atom { if let Variable(v) = atom {
new_bound.insert(Variable(f(*v))); new_bound.insert(Variable(f(*v)));
} else { } else {
new_bound.insert(atom.clone()); new_bound.insert(*atom);
} }
} }
Bool(new_free, new_bound) Bool(new_free, new_bound)

View file

@ -416,7 +416,7 @@ impl Composable for VarUsage {
} }
}; };
self.usage.insert(symbol.clone(), value); self.usage.insert(*symbol, value);
} }
} }
} }

View file

@ -27,7 +27,7 @@ roc_reporting = { path = "../compiler/reporting" }
im = "14" # im and im-rc should always have the same version! im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1.0" inlinable_string = "0.1"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", "process", "io-driver"] } tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", "process", "io-driver"] }
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything. # NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything.
# #
@ -49,27 +49,13 @@ tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded",
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release1" } inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release1" }
target-lexicon = "0.10" target-lexicon = "0.10"
winit = "0.22" winit = "0.22"
image = "0.23" wgpu = "0.5"
gfx-hal = "0.5" glyph_brush = "0.7"
glsl-to-spirv = "0.1" log = "0.4"
bincode = "1.2" zerocopy = "0.3"
serde = { version = "1.0", features = ["derive"] } env_logger = "0.7"
futures = "0.3"
[target.'cfg(target_os = "macos")'.dependencies.backend] wgpu_glyph = "0.9"
package = "gfx-backend-metal"
version = "0.5"
[target.'cfg(windows)'.dependencies.backend]
package = "gfx-backend-dx12"
version = "0.5"
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies.backend]
package = "gfx-backend-vulkan"
features = ["x11"]
version = "0.5"
[build-dependencies]
glsl-to-spirv = "0.1"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1" pretty_assertions = "0.5.1"

Binary file not shown.

BIN
editor/creature.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

704
editor/src/ast.rs Normal file
View file

@ -0,0 +1,704 @@
use inlinable_string::string_ext::StringExt;
use inlinable_string::InlinableString;
use roc_types::subs::Variable;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Problem {
RanOutOfNodeIds,
}
pub type Res<T> = Result<T, Problem>;
/// The index into a decl's array of nodes.
/// This is a u32 index because no decl is allowed to hold more than 2^32 entries,
/// and it saves space on 64-bit systems compared to a pointer.
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct NodeId(u32);
impl NodeId {
pub const NONE: NodeId = NodeId(std::u32::MAX);
pub fn as_index(self) -> usize {
self.0 as usize
}
}
impl fmt::Debug for NodeId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self == &NodeId::NONE {
write!(f, "none")
} else {
write!(f, "#{}", self.0)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UndoAction {
UndoEdit {
caret_id: NodeId,
caret_parent_id: NodeId,
caret_child_id: NodeId,
caret_offset: u16,
child: Node,
redo_action: Action,
},
/// Used in undo logs to mean "the next N undo actions in the log should
/// be replayed together in a batch."
Multiple(u16),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Action {
Backspace,
Paste,
/// Used in redo logs to mean "the next N redo actions in the log should
/// be replayed together in a batch."
Multiple(u16),
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Nodes {
nodes: Vec<Node>,
}
impl Nodes {
pub fn push_expr(&mut self, parent_id: NodeId, expr: Expr) -> Res<NodeId> {
let node_id = self.push_node(Node {
parent_id,
content: NodeContent::Expr(expr),
})?;
self.set_child(parent_id, node_id);
Ok(node_id)
}
fn set_child(&mut self, parent_id: NodeId, _child_id: NodeId) {
match self.nodes.get_mut(parent_id.as_index()) {
Some(parent) => match &mut parent.content {
NodeContent::Expr(Expr::Int { .. }) | NodeContent::Expr(Expr::Float { .. }) => {
// This node has no children. No further action needed!
}
NodeContent::Caret { .. } => {
// This node has no children. No further action needed!
}
other => {
todo!("handle set_child for {:?}", other);
}
},
None => {
// The only reason this bounds check should ever fail
// is that we were passed no parent.
debug_assert!(parent_id == NodeId::NONE);
}
}
}
// fn remove_node(&mut self, target_id: NodeId) {
// debug_assert!(
// target_id != NodeId::NONE,
// "Called remove_node on NodeId::NONE"
// );
// {
// let target = self
// .nodes
// .get_mut(target_id.as_index())
// .unwrap_or_else(|| panic!("Tried to remove nonexistant node {:?}", target_id));
// let opt_parent = self.nodes.get_mut(target.parent_id.as_index());
// match &mut target.content {
// NodeContent::Expr(Expr::Int { .. }) => {
// // This node has no children, so remove it from its parent and move on.
// match opt_parent {
// Some(parent) => parent.remove_child(target_id),
// None => {
// // The only valid reason the node's parent_id might
// // not have been in the nodes vec is that it's NONE.
// debug_assert!(target.parent_id == NodeId::NONE);
// }
// }
// }
// NodeContent::Caret { child_id, offset } => {
// match opt_parent {
// Some(parent) => {
// // Go into the parent and replace this caret with
// // the new child_id
// // parent.replace_child(target_id, child_id)
// }
// None => {
// // The only valid reason the node's parent_id might
// // not have been in the nodes vec is that it's NONE.
// debug_assert!(target.parent_id == NodeId::NONE);
// }
// }
// }
// NodeContent::Removed => {
// panic!("Tried to remove a node that was already removed.");
// }
// }
// }
// }
fn push_node(&mut self, node: Node) -> Res<NodeId> {
// TODO check to see if we have any removed nodes, and if so, use those
// instead of pushing to the end. This way, we can avoid needing to defrag.
// The length *before* we pushed == the index of the node we pushed.
let index = self.nodes.len();
self.nodes.push(node);
if index < std::u32::MAX as usize {
Ok(NodeId(index as u32))
} else {
// u32::MAX is reserved for NodeId::NONE, so if we hit that on a
// saturating add, we've overflowed. Game over.
Err(Problem::RanOutOfNodeIds)
}
}
pub fn replace(&mut self, node_id: NodeId, node: Node) {
let elem = self.nodes.get_mut(node_id.as_index()).unwrap_or_else(|| {
panic!(
"Tried to replace element at nonexistant NodeId {:?} with {:?}",
node_id, node
)
});
*elem = node;
}
pub fn wrap_with_caret(&mut self, target_id: NodeId, offset: u16) -> Res<NodeId> {
let parent_id = self.get(target_id).parent_id;
let caret_id = {
let content = NodeContent::Caret {
child_id: target_id,
offset,
};
self.push_node(Node { content, parent_id })?
};
// Now that we have the caret_id, update the old
if parent_id != NodeId::NONE {
self.get_mut(target_id).replace_child(target_id, caret_id);
}
Ok(caret_id)
}
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn split_at_mut(&mut self, index: NodeId) -> (&mut [Node], &mut [Node]) {
self.nodes.split_at_mut(index.0 as usize)
}
pub fn get(&self, index: NodeId) -> &Node {
self.nodes
.get(index.0 as usize)
.unwrap_or_else(|| panic!("Unable to find node at index {:?}", index.0))
}
pub fn get_mut(&mut self, index: NodeId) -> &mut Node {
self.nodes
.get_mut(index.0 as usize)
.unwrap_or_else(|| panic!("Unable to find node at index {:?}", index.0))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Node {
parent_id: NodeId,
content: NodeContent,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NodeContent {
Expr(Expr /* TODO should Node have a Variable? */),
// Pattern(Pattern),
Caret { child_id: NodeId, offset: u16 },
// SelectionStart { offset: u16, child: NodeId },
// SelectionEnd { offset: u16, child: NodeId },
Removed,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expr {
/// An integer literal (without a dot)
Int {
text: InlinableString,
var: Variable,
},
/// An floating-point literal (with a dot)
Float {
text: InlinableString,
var: Variable,
},
// /// A partial lookup that has not yet been completed, e.g.
// /// `Foo.` or `pkg.Foo.Bar`
// PartialLookup {
// /// dot-separated sections, e.g. `Foo.Bar.` would be ["Foo", "Bar", ""]
// sections: Vec<InlinableString>,
// var: Variable,
// },
// Lookup {
// name: InlinableString,
// var: Variable,
// },
// If {
// conditional: NodeId,
// then_node: NodeId,
// else_node: NodeId,
// var: Variable,
// },
// Then {
// child: NodeId,
// var: Variable,
// },
// Else {
// child: NodeId,
// var: Variable,
// },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Pattern {
Identifier { text: String, var: Variable },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Decl {
Def(Def),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Def {
Body { pattern: NodeId, expr: NodeId },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Module {
pub nodes: Nodes,
pub decls: Vec<Decl>,
// Use a Vec over a Set because it'll always be of small length
pub carets: Vec<NodeId>,
pub selections: Vec<(NodeId, NodeId)>,
/// Because these actions store NodeId values, it's critically important
/// that when we take an action and then redo it, everything (including Nodes)
/// ends up back in the same state, including NodeId values.
pub undo_log: Vec<UndoAction>,
pub redos: Vec<Action>,
}
impl Node {
/// Returns the number of characters removed, so that
/// the caret can be moved back this many indices.
/// (For example, if you backspace over an emoji, that can move the
/// caret back multiple indices.)
pub fn backspace(&mut self, index: u16) -> u16 {
use Expr::*;
match &mut self.content {
NodeContent::Expr(Int { text, .. }) | NodeContent::Expr(Float { text, .. }) => {
// TODO remove an entire *grapheme cluster* here, not just a
// single character! This will require using a 3rd-party crate.
let removed = text.remove(index as usize);
// TODO need to re-index any other carets/selections which
// were also in this node - they need to be moved too!
removed.len_utf8() as u16
}
other => {
todo!("handle backspace for {:?}", other);
}
}
}
// fn remove_child(&mut self, id_to_remove: NodeId) {
// match &mut self.content {
// NodeContent::Expr(Expr::Int { .. }) | NodeContent::Expr(Expr::Float { .. }) => {
// // This node has no children, so no action is needed.
// }
// NodeContent::Caret { child_id, .. } => {
// if *child_id == id_to_remove {
// *child_id = NodeId::NONE;
// }
// }
// NodeContent::Removed => {
// panic!(
// "Tried to remove a child node ({:?}) from a NodeContent::Removed",
// id_to_remove
// );
// }
// }
// }
fn replace_child(&mut self, old_id: NodeId, new_id: NodeId) {
match &mut self.content {
NodeContent::Expr(Expr::Int { .. }) | NodeContent::Expr(Expr::Float { .. }) => {
// This node has no children, so no action is needed.
}
NodeContent::Caret { child_id, .. } => {
if *child_id == old_id {
*child_id = new_id;
}
}
NodeContent::Removed => {
panic!(
"Tried to replace child node ID {:?} with {:?} in a NodeContent::Removed",
old_id, new_id
);
}
}
}
}
impl Module {
pub fn undo(&mut self) {
let mut iterations_remaining: u16 = 0;
loop {
match self.undo_log.pop() {
Some(action) => {
iterations_remaining += self.apply_undo_action(action);
}
None => {
if iterations_remaining > 0 {
panic!("Expected to be able to do {} more Undo iterations, but ran out of actions to undo.", iterations_remaining);
}
}
}
if iterations_remaining == 0 {
return;
} else {
iterations_remaining -= 1;
}
}
}
pub fn redo(&mut self) {
let mut iterations_remaining: u16 = 0;
loop {
match self.redos.pop() {
Some(action) => {
iterations_remaining += self.apply_action(action);
}
None => {
if iterations_remaining > 0 {
panic!("Expected to be able to do {} more Redo iterations, but ran out of actions to redo.", iterations_remaining);
}
}
}
if iterations_remaining == 0 {
return;
} else {
iterations_remaining -= 1;
}
}
}
fn apply_action(&mut self, action: Action) -> u16 {
use Action::*;
match action {
Backspace => {
self.backspace();
0
}
Paste => {
todo!("TODO support Paste action");
}
Multiple(iterations) => iterations,
}
}
fn apply_undo_action(&mut self, action: UndoAction) -> u16 {
use UndoAction::*;
match action {
UndoEdit {
caret_id,
caret_parent_id,
caret_child_id,
caret_offset,
child,
redo_action,
} => {
self.redos.push(redo_action);
self.nodes.replace(caret_child_id, child);
let caret = Node {
parent_id: caret_parent_id,
content: NodeContent::Caret {
child_id: caret_child_id,
offset: caret_offset,
},
};
self.nodes.replace(caret_id, caret);
0
}
Multiple(iterations) => iterations,
}
}
pub fn paste(&mut self, text: &str) {
todo!(
"TODO paste this string, taking carets and selections into account: {:?}",
text
);
}
pub fn backspace(&mut self) {
for &caret_node_id in self.carets.iter() {
debug_assert!(caret_node_id.as_index() <= self.nodes.len());
// Use slices around the caret because we'll need to modify the
// child node. Without these slices we'd need multiple simultaneous
// mutable references to self.nodes, which is never allowed.
let (before_caret, caret_and_after) = self.nodes.split_at_mut(caret_node_id);
let (caret_only, after_caret) = caret_and_after.split_at_mut(1);
let caret = caret_only.first_mut().unwrap();
let parent_id = caret.parent_id;
match &mut caret.content {
NodeContent::Caret { offset, child_id } => {
let child_id = *child_id;
let offset_index = *offset;
if offset_index != 0 {
// Get the child node from the appropriate slice
let child_node: &mut Node = {
debug_assert!(
child_id != caret_node_id,
"A caret had itself as its own child: {:?}",
caret_node_id
);
if child_id > caret_node_id {
after_caret.get_mut(child_id.as_index() - (before_caret.len() + 1))
} else {
before_caret.get_mut(child_id.as_index())
}
}
.unwrap_or_else(|| {
panic!("Could not get child node for caret {:?}", caret_node_id)
});
// Add an entry to the undo log to undo this edit.
self.undo_log.push(UndoAction::UndoEdit {
caret_id: caret_node_id,
caret_parent_id: parent_id,
caret_offset: offset_index,
caret_child_id: child_id,
child: child_node.clone(),
redo_action: Action::Backspace,
});
// Mutate the child node to apply the backspace operation.
//
// -1 because index 0 is *before* the first character
// in the string (which we already ruled out using the
// above conditional), and child_node.backspace expects
// to be given the index *after* the char to be removed.
let chars_removed = child_node.backspace(offset_index - 1);
// Mutate the caret to decrement its offset
*offset = offset_index - chars_removed;
} else {
todo!(
"Backspace when caret offset is 0, into parent: {:?}",
parent_id
);
}
}
other => {
unreachable!("Caret pointed to a non-caret node: {:?}", other);
}
}
}
}
}
#[test]
fn single_backspace_1_caret() {
use roc_types::subs::VarStore;
let var_store = VarStore::default();
let int_var = var_store.fresh();
let int_node_id;
let caret_node_id;
let expected = {
let mut nodes = Nodes::default();
int_node_id = nodes
.push_expr(
NodeId::NONE,
Expr::Int {
text: "abd".into(),
var: int_var,
},
)
.unwrap();
caret_node_id = nodes.wrap_with_caret(int_node_id, 2).unwrap();
nodes
};
let actual = {
let mut nodes = Nodes::default();
let actual_node_id = nodes
.push_expr(
NodeId::NONE,
Expr::Int {
text: "abcd".into(),
var: int_var,
},
)
.unwrap();
assert!(int_node_id == actual_node_id);
let actual_node_id = nodes.wrap_with_caret(int_node_id, 3).unwrap();
assert!(caret_node_id == actual_node_id);
nodes
};
let mut module = Module {
nodes: actual,
decls: Vec::new(),
carets: vec![caret_node_id],
selections: Vec::new(),
undo_log: Vec::new(),
redos: Vec::new(),
};
let original_module = module.clone();
module.backspace();
let altered_module = module.clone();
assert_eq!(expected, module.nodes);
module.undo();
let stashed_redos = module.redos;
module.redos = Vec::new();
assert_eq!(original_module, module);
module.redos = stashed_redos;
module.redo();
assert_eq!(altered_module, module);
}
#[test]
fn double_backspace_1_caret() {
use roc_types::subs::VarStore;
let var_store = VarStore::default();
let int_var = var_store.fresh();
let int_node_id;
let caret_node_id;
let expected = {
let mut nodes = Nodes::default();
int_node_id = nodes
.push_expr(
NodeId::NONE,
Expr::Int {
text: "ad".into(),
var: int_var,
},
)
.unwrap();
caret_node_id = nodes.wrap_with_caret(int_node_id, 1).unwrap();
nodes
};
let actual = {
let mut nodes = Nodes::default();
let actual_node_id = nodes
.push_expr(
NodeId::NONE,
Expr::Int {
text: "abcd".into(),
var: int_var,
},
)
.unwrap();
assert!(int_node_id == actual_node_id);
assert!(caret_node_id == nodes.wrap_with_caret(int_node_id, 3).unwrap());
nodes
};
let mut module = Module {
nodes: actual,
decls: Vec::new(),
carets: vec![caret_node_id],
selections: Vec::new(),
undo_log: Vec::new(),
redos: Vec::new(),
};
let original_module = module.clone();
module.backspace();
module.backspace();
let altered_module = module.clone();
assert_eq!(expected, module.nodes);
module.undo();
module.undo();
let stashed_redos = module.redos;
module.redos = Vec::new();
assert_eq!(original_module, module);
module.redos = stashed_redos;
module.redo();
module.redo();
assert_eq!(altered_module, module);
}

View file

@ -1,326 +1,99 @@
use gfx_hal::{ #![warn(clippy::all, clippy::dbg_macro)]
device::Device, // I'm skeptical that clippy:large_enum_variant is a good lint to have globally enabled.
window::{Extent2D, PresentationSurface, Surface}, //
Instance, // It warns about a performance problem where the only quick remediation is
}; // to allocate more on the heap, which has lots of tradeoffs - including making it
use glsl_to_spirv::ShaderType; // long-term unclear which allocations *need* to happen for compilation's sake
// (e.g. recursive structures) versus those which were only added to appease clippy.
//
// Effectively optimizing data struture memory layout isn't a quick fix,
// and encouraging shortcuts here creates bad incentives. I would rather temporarily
// re-enable this when working on performance optimizations than have it block PRs.
#![allow(clippy::large_enum_variant)]
use std::error::Error;
use std::io; use std::io;
use std::mem::ManuallyDrop;
use std::path::Path; use std::path::Path;
use wgpu_glyph::{ab_glyph, GlyphBrushBuilder, Section, Text};
use winit::event::{ElementState, ModifiersState, VirtualKeyCode}; use winit::event::{ElementState, ModifiersState, VirtualKeyCode};
use winit::event_loop::ControlFlow;
pub mod ast;
pub mod text_state;
/// The editor is actually launched from the CLI if you pass it zero arguments, /// The editor is actually launched from the CLI if you pass it zero arguments,
/// or if you provide it 1 or more files or directories to open on launch. /// or if you provide it 1 or more files or directories to open on launch.
pub fn launch(_filepaths: &[&Path]) -> io::Result<()> { pub fn launch(_filepaths: &[&Path]) -> io::Result<()> {
// TODO do any initialization here // TODO do any initialization here
run_event_loop(); run_event_loop().expect("Error running event loop");
Ok(()) Ok(())
} }
use winit::{event_loop::EventLoop, window::WindowBuilder}; fn run_event_loop() -> Result<(), Box<dyn Error>> {
env_logger::init();
struct Resources<B: gfx_hal::Backend> { // Open window and create a surface
instance: B::Instance, let event_loop = winit::event_loop::EventLoop::new();
surface: B::Surface,
device: B::Device,
render_passes: Vec<B::RenderPass>,
pipeline_layouts: Vec<B::PipelineLayout>,
pipelines: Vec<B::GraphicsPipeline>,
command_pool: B::CommandPool,
submission_complete_fence: B::Fence,
rendering_complete_semaphore: B::Semaphore,
}
struct ResourceHolder<B: gfx_hal::Backend>(ManuallyDrop<Resources<B>>); let window = winit::window::WindowBuilder::new()
impl<B: gfx_hal::Backend> Drop for ResourceHolder<B> {
fn drop(&mut self) {
unsafe {
let Resources {
instance,
mut surface,
device,
command_pool,
render_passes,
pipeline_layouts,
pipelines,
submission_complete_fence,
rendering_complete_semaphore,
} = ManuallyDrop::take(&mut self.0);
device.destroy_semaphore(rendering_complete_semaphore);
device.destroy_fence(submission_complete_fence);
for pipeline in pipelines {
device.destroy_graphics_pipeline(pipeline);
}
for pipeline_layout in pipeline_layouts {
device.destroy_pipeline_layout(pipeline_layout);
}
for render_pass in render_passes {
device.destroy_render_pass(render_pass);
}
device.destroy_command_pool(command_pool);
surface.unconfigure_swapchain(&device);
instance.destroy_surface(surface);
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct PushConstants {
color: [f32; 4],
pos: [f32; 2],
scale: [f32; 2],
}
fn run_event_loop() {
// TODO do a better window size
const WINDOW_SIZE: [u32; 2] = [512, 512];
// TODO try configuring the swapchain explicitly, in particular in order
// to experiment with different PresentMode settings to see how they
// affect input latency.
//
// https://rust-tutorials.github.io/learn-gfx-hal/03_clear_the_window.html
let event_loop = EventLoop::new();
let (logical_window_size, physical_window_size) = {
use winit::dpi::{LogicalSize, PhysicalSize};
let dpi = event_loop.primary_monitor().scale_factor();
let logical: LogicalSize<u32> = WINDOW_SIZE.into();
let physical: PhysicalSize<u32> = logical.to_physical(dpi);
(logical, physical)
};
let mut surface_extent = Extent2D {
width: physical_window_size.width,
height: physical_window_size.height,
};
let window = WindowBuilder::new()
.with_title("roc")
.with_inner_size(logical_window_size)
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
let mut should_configure_swapchain = true; let surface = wgpu::Surface::create(&window);
let (instance, surface, adapter) = { // Initialize GPU
let instance = backend::Instance::create("roc_editor", 1).expect("Backend not supported"); let (device, queue) = futures::executor::block_on(async {
let adapter = wgpu::Adapter::request(
let surface = unsafe { &wgpu::RequestAdapterOptions {
instance power_preference: wgpu::PowerPreference::HighPerformance,
.create_surface(&window) compatible_surface: Some(&surface),
.expect("Failed to create surface for window") },
}; wgpu::BackendBit::all(),
)
let adapter = instance.enumerate_adapters().remove(0); .await
.expect("Request adapter");
(instance, surface, adapter)
};
let (device, mut queue_group) = {
use gfx_hal::queue::QueueFamily;
let queue_family = adapter
.queue_families
.iter()
.find(|family| {
surface.supports_queue_family(family) && family.queue_type().supports_graphics()
})
.expect("No compatible queue family found");
let mut gpu = unsafe {
use gfx_hal::adapter::PhysicalDevice;
adapter adapter
.physical_device .request_device(&wgpu::DeviceDescriptor {
.open(&[(queue_family, &[1.0])], gfx_hal::Features::empty()) extensions: wgpu::Extensions {
.expect("Failed to open device") anisotropic_filtering: false,
};
(gpu.device, gpu.queue_groups.pop().unwrap())
};
let (command_pool, mut command_buffer) = unsafe {
use gfx_hal::command::Level;
use gfx_hal::pool::{CommandPool, CommandPoolCreateFlags};
let mut command_pool = device
.create_command_pool(queue_group.family, CommandPoolCreateFlags::empty())
.expect("Out of memory");
let command_buffer = command_pool.allocate_one(Level::Primary);
(command_pool, command_buffer)
};
let surface_color_format = {
use gfx_hal::format::{ChannelType, Format};
let supported_formats = surface
.supported_formats(&adapter.physical_device)
.unwrap_or_else(|| vec![]);
let default_format = *supported_formats.get(0).unwrap_or(&Format::Rgba8Srgb);
supported_formats
.into_iter()
.find(|format| format.base_format().1 == ChannelType::Srgb)
.unwrap_or(default_format)
};
let render_pass = {
use gfx_hal::image::Layout;
use gfx_hal::pass::{
Attachment, AttachmentLoadOp, AttachmentOps, AttachmentStoreOp, SubpassDesc,
};
let color_attachment = Attachment {
format: Some(surface_color_format),
samples: 1,
ops: AttachmentOps::new(AttachmentLoadOp::Clear, AttachmentStoreOp::Store),
stencil_ops: AttachmentOps::DONT_CARE,
layouts: Layout::Undefined..Layout::Present,
};
let subpass = SubpassDesc {
colors: &[(0, Layout::ColorAttachmentOptimal)],
depth_stencil: None,
inputs: &[],
resolves: &[],
preserves: &[],
};
unsafe {
device
.create_render_pass(&[color_attachment], &[subpass], &[])
.expect("Out of memory")
}
};
let pipeline_layout = unsafe {
use gfx_hal::pso::ShaderStageFlags;
let push_constant_bytes = std::mem::size_of::<PushConstants>() as u32;
device
.create_pipeline_layout(&[], &[(ShaderStageFlags::VERTEX, 0..push_constant_bytes)])
.expect("Out of memory")
};
let vertex_shader = include_str!("../shaders/triangle.vert");
let fragment_shader = include_str!("../shaders/triangle.frag");
/// Create a pipeline with the given layout and shaders.
unsafe fn make_pipeline<B: gfx_hal::Backend>(
device: &B::Device,
render_pass: &B::RenderPass,
pipeline_layout: &B::PipelineLayout,
vertex_shader: &str,
fragment_shader: &str,
) -> B::GraphicsPipeline {
use gfx_hal::pass::Subpass;
use gfx_hal::pso::{
BlendState, ColorBlendDesc, ColorMask, EntryPoint, Face, GraphicsPipelineDesc,
GraphicsShaderSet, Primitive, Rasterizer, Specialization,
};
let vertex_shader_module = device
.create_shader_module(&compile_shader(vertex_shader, ShaderType::Vertex))
.expect("Failed to create vertex shader module");
let fragment_shader_module = device
.create_shader_module(&compile_shader(fragment_shader, ShaderType::Fragment))
.expect("Failed to create fragment shader module");
let (vs_entry, fs_entry) = (
EntryPoint {
entry: "main",
module: &vertex_shader_module,
specialization: Specialization::default(),
}, },
EntryPoint { limits: wgpu::Limits { max_bind_groups: 1 },
entry: "main", })
module: &fragment_shader_module, .await
specialization: Specialization::default(),
},
);
let shader_entries = GraphicsShaderSet {
vertex: vs_entry,
hull: None,
domain: None,
geometry: None,
fragment: Some(fs_entry),
};
let mut pipeline_desc = GraphicsPipelineDesc::new(
shader_entries,
Primitive::TriangleList,
Rasterizer {
cull_face: Face::BACK,
..Rasterizer::FILL
},
pipeline_layout,
Subpass {
index: 0,
main_pass: render_pass,
},
);
pipeline_desc.blender.targets.push(ColorBlendDesc {
mask: ColorMask::ALL,
blend: Some(BlendState::ALPHA),
}); });
let pipeline = device // Prepare swap chain
.create_graphics_pipeline(&pipeline_desc, None) let render_format = wgpu::TextureFormat::Bgra8UnormSrgb;
.expect("Failed to create graphics pipeline"); let mut size = window.inner_size();
device.destroy_shader_module(vertex_shader_module); let mut swap_chain = device.create_swap_chain(
device.destroy_shader_module(fragment_shader_module); &surface,
&wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: render_format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Immediate,
},
);
pipeline // Prepare glyph_brush
}; let inconsolata =
ab_glyph::FontArc::try_from_slice(include_bytes!("../Inconsolata-Regular.ttf"))?;
let pipeline = unsafe { let mut glyph_brush = GlyphBrushBuilder::using_font(inconsolata).build(&device, render_format);
make_pipeline::<backend::Backend>(
&device,
&render_pass,
&pipeline_layout,
vertex_shader,
fragment_shader,
)
};
let submission_complete_fence = device.create_fence(true).expect("Out of memory");
let rendering_complete_semaphore = device.create_semaphore().expect("Out of memory");
let mut resource_holder: ResourceHolder<backend::Backend> =
ResourceHolder(ManuallyDrop::new(Resources {
instance,
surface,
device,
command_pool,
render_passes: vec![render_pass],
pipeline_layouts: vec![pipeline_layout],
pipelines: vec![pipeline],
submission_complete_fence,
rendering_complete_semaphore,
}));
let is_animating = true; let is_animating = true;
let mut text_state = String::new(); let mut text_state = String::new();
let mut keyboard_modifiers = ModifiersState::empty(); let mut keyboard_modifiers = ModifiersState::empty();
event_loop.run(move |event, _, control_flow| { // Render loop
use winit::event::{Event, WindowEvent}; window.request_redraw();
use winit::event_loop::ControlFlow;
event_loop.run(move |event, _, control_flow| {
// TODO dynamically switch this on/off depending on whether any // TODO dynamically switch this on/off depending on whether any
// animations are running. Should conserve CPU usage and battery life! // animations are running. Should conserve CPU usage and battery life!
if is_animating { if is_animating {
@ -330,24 +103,56 @@ fn run_event_loop() {
} }
match event { match event {
Event::WindowEvent { winit::event::Event::WindowEvent {
event: window_event, event: winit::event::WindowEvent::CloseRequested,
.. ..
} => match window_event { } => *control_flow = winit::event_loop::ControlFlow::Exit,
WindowEvent::CloseRequested => { winit::event::Event::WindowEvent {
println!("✈️ Thank you for flying Roc Airlines!"); event: winit::event::WindowEvent::Resized(new_size),
*control_flow = ControlFlow::Exit ..
} => {
size = new_size;
swap_chain = device.create_swap_chain(
&surface,
&wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: render_format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Immediate,
},
);
} }
WindowEvent::Resized(dims) => { winit::event::Event::WindowEvent {
surface_extent = Extent2D { event: winit::event::WindowEvent::ReceivedCharacter(ch),
width: dims.width, ..
height: dims.height, } => {
}; match ch {
should_configure_swapchain = true; '\u{8}' => {
// In Linux, we get one of these when you press
// backspace, but in macOS we don't. In both, we
// get a Back keydown event. Therefore, we use the
// Back keydown event and ignore this, resulting
// in a system that works in both Linux and macOS.
} }
WindowEvent::KeyboardInput { input, .. } => { '\u{e000}'..='\u{f8ff}'
| '\u{f0000}'..='\u{ffffd}'
| '\u{100000}'..='\u{10fffd}' => {
// These are private use characters; ignore them.
// See http://www.unicode.org/faq/private_use.html
}
_ => {
text_state.push(ch);
}
}
}
winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::KeyboardInput { input, .. },
..
} => {
if let Some(virtual_keycode) = input.virtual_keycode { if let Some(virtual_keycode) = input.virtual_keycode {
handle_text_input( handle_keydown(
&mut text_state, &mut text_state,
input.state, input.state,
virtual_keycode, virtual_keycode,
@ -355,215 +160,74 @@ fn run_event_loop() {
); );
} }
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { winit::event::Event::WindowEvent {
surface_extent = Extent2D { event: winit::event::WindowEvent::ModifiersChanged(modifiers),
width: new_inner_size.width, ..
height: new_inner_size.height, } => {
};
should_configure_swapchain = true;
}
WindowEvent::ModifiersChanged(modifiers) => {
keyboard_modifiers = modifiers; keyboard_modifiers = modifiers;
} }
_ => (), winit::event::Event::MainEventsCleared => window.request_redraw(),
}, winit::event::Event::RedrawRequested { .. } => {
Event::MainEventsCleared => window.request_redraw(), // Get a command encoder for the current frame
Event::RedrawRequested(_) => { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
let res: &mut Resources<_> = &mut resource_holder.0; label: Some("Redraw"),
let render_pass = &res.render_passes[0];
let pipeline_layout = &res.pipeline_layouts[0];
let pipeline = &res.pipelines[0];
let triangles = text_state.chars().enumerate().map(|(index, char)| {
if char == ' ' {
PushConstants {
color: [0.0, 0.0, 0.0, 0.0],
pos: [0.0, 0.0],
scale: [0.00, 0.00],
}
} else {
PushConstants {
color: [1.0, 1.0, 1.0, 1.0],
pos: [0.06 * index as f32, 0.0],
scale: [0.05, 0.05],
}
}
}); });
unsafe { // Get the next frame
use gfx_hal::pool::CommandPool; let frame = swap_chain.get_next_texture().expect("Get next frame");
// We refuse to wait more than a second, to avoid hanging. // Clear frame
let render_timeout_ns = 1_000_000_000; {
let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
res.device color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
.wait_for_fence(&res.submission_complete_fence, render_timeout_ns) attachment: &frame.view,
.expect("Out of memory or device lost"); resolve_target: None,
load_op: wgpu::LoadOp::Clear,
res.device store_op: wgpu::StoreOp::Store,
.reset_fence(&res.submission_complete_fence) clear_color: wgpu::Color {
.expect("Out of memory"); r: 0.007,
g: 0.007,
res.command_pool.reset(false); b: 0.007,
} a: 1.0,
if should_configure_swapchain {
use gfx_hal::window::SwapchainConfig;
let caps = res.surface.capabilities(&adapter.physical_device);
let mut swapchain_config =
SwapchainConfig::from_caps(&caps, surface_color_format, surface_extent);
// This seems to fix some fullscreen slowdown on macOS.
if caps.image_count.contains(&3) {
swapchain_config.image_count = 3;
}
surface_extent = swapchain_config.extent;
unsafe {
res.surface
.configure_swapchain(&res.device, swapchain_config)
.expect("Failed to configure swapchain");
};
should_configure_swapchain = false;
}
let surface_image = unsafe {
// We refuse to wait more than a second, to avoid hanging.
let acquire_timeout_ns = 1_000_000_000;
match res.surface.acquire_image(acquire_timeout_ns) {
Ok((image, _)) => image,
Err(_) => {
should_configure_swapchain = true;
return;
}
}
};
let framebuffer = unsafe {
use std::borrow::Borrow;
use gfx_hal::image::Extent;
res.device
.create_framebuffer(
render_pass,
vec![surface_image.borrow()],
Extent {
width: surface_extent.width,
height: surface_extent.height,
depth: 1,
},
)
.unwrap()
};
let viewport = {
use gfx_hal::pso::{Rect, Viewport};
Viewport {
rect: Rect {
x: 0,
y: 0,
w: surface_extent.width as i16,
h: surface_extent.height as i16,
},
depth: 0.0..1.0,
}
};
unsafe {
use gfx_hal::command::{
ClearColor, ClearValue, CommandBuffer, CommandBufferFlags, SubpassContents,
};
command_buffer.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT);
command_buffer.set_viewports(0, &[viewport.clone()]);
command_buffer.set_scissors(0, &[viewport.rect]);
command_buffer.begin_render_pass(
render_pass,
&framebuffer,
viewport.rect,
&[ClearValue {
color: ClearColor {
float32: [0.0, 0.0, 0.0, 1.0],
}, },
}], }],
SubpassContents::Inline, depth_stencil_attachment: None,
);
command_buffer.bind_graphics_pipeline(pipeline);
for triangle in triangles {
use gfx_hal::pso::ShaderStageFlags;
command_buffer.push_graphics_constants(
pipeline_layout,
ShaderStageFlags::VERTEX,
0,
push_constant_bytes(&triangle),
);
command_buffer.draw(0..3, 0..1);
}
command_buffer.end_render_pass();
command_buffer.finish();
}
unsafe {
use gfx_hal::queue::{CommandQueue, Submission};
let submission = Submission {
command_buffers: vec![&command_buffer],
wait_semaphores: None,
signal_semaphores: vec![&res.rendering_complete_semaphore],
};
queue_group.queues[0].submit(submission, Some(&res.submission_complete_fence));
let result = queue_group.queues[0].present_surface(
&mut res.surface,
surface_image,
Some(&res.rendering_complete_semaphore),
);
should_configure_swapchain |= result.is_err();
res.device.destroy_framebuffer(framebuffer);
}
}
_ => (),
}
}); });
} }
/// Compile some GLSL shader source to SPIR-V. glyph_brush.queue(Section {
/// TODO do this at build time - possibly in CI only screen_position: (30.0, 30.0),
fn compile_shader(glsl: &str, shader_type: ShaderType) -> Vec<u32> { bounds: (size.width as f32, size.height as f32),
use std::io::{Cursor, Read}; text: vec![Text::new("Enter some text:")
.with_color([0.4666, 0.2, 1.0, 1.0])
.with_scale(40.0)],
..Section::default()
});
let mut compiled_file = glyph_brush.queue(Section {
glsl_to_spirv::compile(glsl, shader_type).expect("Failed to compile shader"); screen_position: (30.0, 90.0),
bounds: (size.width as f32, size.height as f32),
text: vec![Text::new(format!("{}|", text_state).as_str())
.with_color([1.0, 1.0, 1.0, 1.0])
.with_scale(40.0)],
..Section::default()
});
let mut spirv_bytes = vec![]; // Draw the text!
compiled_file.read_to_end(&mut spirv_bytes).unwrap(); glyph_brush
.draw_queued(&device, &mut encoder, &frame.view, size.width, size.height)
.expect("Draw queued");
gfx_hal::pso::read_spirv(Cursor::new(&spirv_bytes)).expect("Invalid SPIR-V") queue.submit(&[encoder.finish()]);
}
_ => {
*control_flow = winit::event_loop::ControlFlow::Wait;
}
}
})
} }
/// Returns a view of a struct as a slice of `u32`s. fn handle_keydown(
unsafe fn push_constant_bytes<T>(push_constants: &T) -> &[u32] {
let size_in_bytes = std::mem::size_of::<T>();
let size_in_u32s = size_in_bytes / std::mem::size_of::<u32>();
let start_ptr = push_constants as *const T as *const u32;
std::slice::from_raw_parts(start_ptr, size_in_u32s)
}
fn handle_text_input(
text_state: &mut String, text_state: &mut String,
elem_state: ElementState, elem_state: ElementState,
virtual_keycode: VirtualKeyCode, virtual_keycode: VirtualKeyCode,
@ -576,109 +240,12 @@ fn handle_text_input(
} }
match virtual_keycode { match virtual_keycode {
Key1 | Numpad1 => text_state.push_str("1"),
Key2 | Numpad2 => text_state.push_str("2"),
Key3 | Numpad3 => text_state.push_str("3"),
Key4 | Numpad4 => text_state.push_str("4"),
Key5 | Numpad5 => text_state.push_str("5"),
Key6 | Numpad6 => text_state.push_str("6"),
Key7 | Numpad7 => text_state.push_str("7"),
Key8 | Numpad8 => text_state.push_str("8"),
Key9 | Numpad9 => text_state.push_str("9"),
Key0 | Numpad0 => text_state.push_str("0"),
A => text_state.push_str("a"),
B => text_state.push_str("b"),
C => text_state.push_str("c"),
D => text_state.push_str("d"),
E => text_state.push_str("e"),
F => text_state.push_str("f"),
G => text_state.push_str("g"),
H => text_state.push_str("h"),
I => text_state.push_str("i"),
J => text_state.push_str("j"),
K => text_state.push_str("k"),
L => text_state.push_str("l"),
M => text_state.push_str("m"),
N => text_state.push_str("n"),
O => text_state.push_str("o"),
P => text_state.push_str("p"),
Q => text_state.push_str("q"),
R => text_state.push_str("r"),
S => text_state.push_str("s"),
T => text_state.push_str("t"),
U => text_state.push_str("u"),
V => text_state.push_str("v"),
W => text_state.push_str("w"),
X => text_state.push_str("x"),
Y => text_state.push_str("y"),
Z => text_state.push_str("z"),
Escape | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9 | F10 | F11 | F12 | F13 | F14 | F15
| F16 | F17 | F18 | F19 | F20 | F21 | F22 | F23 | F24 | Snapshot | Scroll | Pause
| Insert | Home | Delete | End | PageDown | PageUp | Left | Up | Right | Down | Compose
| Caret | Numlock | AbntC1 | AbntC2 | Ax | Calculator | Capital | Convert | Kana
| Kanji | LAlt | LBracket | LControl | LShift | LWin | Mail | MediaSelect | PlayPause
| Power | PrevTrack | MediaStop | Mute | MyComputer | NavigateForward
| NavigateBackward | NextTrack | NoConvert | OEM102 | RAlt | Sysrq | RBracket
| RControl | RShift | RWin | Sleep | Stop | Unlabeled | VolumeDown | VolumeUp | Wake
| WebBack | WebFavorites | WebForward | WebHome | WebRefresh | WebSearch | Apps | Tab
| WebStop => {
// TODO handle
dbg!(virtual_keycode);
}
Back => { Back => {
// Backspace deletes a character.
// In Linux, we get a Unicode character for backspace events
// (which is handled elsewhere), but on macOS we only get one of these.
text_state.pop(); text_state.pop();
} }
Return | NumpadEnter => {
text_state.push_str("\n");
}
Space => {
text_state.push_str(" ");
}
Comma | NumpadComma => {
text_state.push_str(",");
}
Add => {
text_state.push_str("+");
}
Apostrophe => {
text_state.push_str("'");
}
At => {
text_state.push_str("@");
}
Backslash => {
text_state.push_str("\\");
}
Colon => {
text_state.push_str(":");
}
Period | Decimal => {
text_state.push_str(".");
}
Equals | NumpadEquals => {
text_state.push_str("=");
}
Grave => {
text_state.push_str("`");
}
Minus | Subtract => {
text_state.push_str("-");
}
Multiply => {
text_state.push_str("*");
}
Semicolon => {
text_state.push_str(";");
}
Slash | Divide => {
text_state.push_str("/");
}
Underline => {
text_state.push_str("_");
}
Yen => {
text_state.push_str("¥");
}
Copy => { Copy => {
todo!("copy"); todo!("copy");
} }
@ -688,5 +255,6 @@ fn handle_text_input(
Cut => { Cut => {
todo!("cut"); todo!("cut");
} }
_ => {}
} }
} }

128
editor/src/text_state.rs Normal file
View file

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

View file

@ -1,4 +0,0 @@
<svg viewBox="0 0 52 53" xmlns="http://www.w3.org/2000/svg">
<style>polygon {fill: #5c0bff;}@media (prefers-color-scheme: dark) {polygon {fill: #7733ff;}} </style>
<polygon points="0,0 23.8834,3.21052 37.2438,19.0101 45.9665,16.6324 50.5,22 45,22 44.0315,26.3689 26.4673,39.3424 27.4527,45.2132 17.655,53 23.6751,22.7086"/>
</svg>

Before

Width:  |  Height:  |  Size: 335 B

View file

@ -1,145 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>The Roc Programming Language</title>
<meta name="description" content="A language for building fast, reliable software.">
<meta name="viewport" content="width=device-width">
<link rel="icon" href="favicon.svg">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<div class="content">
<nav class="sidebar">
<h2 id="sidebar-heading">modules</h2>
<input id="module-search" aria-labelledby="sidebar-heading" type="text" placeholder="Search" tabindex=0 />
<a class="sidebar-link" href="#Bool">Bool</a>
<a class="sidebar-link" href="#Num">Num</a>
<a class="sidebar-link" href="#Str">Str</a>
<a class="sidebar-link" href="#List">List</a>
<a class="sidebar-link" href="#Set">Set</a>
<a class="sidebar-link" href="#Map">Map</a>
<a class="sidebar-link" href="#Result">Result</a>
</nav>
<div class="main-container">
<header class="top-header">
<nav class="main-nav">
<div class="pkg-and-logo">
<a class="logo" href="/" aria-labelledby="logo-link">
<svg viewBox="0 -6 51 58" fill="none" xmlns="http://www.w3.org/2000/svg" aria-labelledby="logo-link" role="img">
<title id="logo-link">Return to Roc packages</title>
<defs>
<linearGradient id="logo-gradient" x2="0.35" y2="1">
<stop offset="0%" stop-color="var(--logo-gradient-start)" />
<stop offset="50%" stop-color="var(--logo-gradient-mid)" />
<stop offset="85%" stop-color="var(--logo-gradient-end)" />
</linearGradient>
</defs>
<polygon role="presentation" points="0,0 23.8834,3.21052 37.2438,19.0101 45.9665,16.6324 50.5,22 45,22 44.0315,26.3689 26.4673,39.3424 27.4527,45.2132 17.655,53 23.6751,22.7086" class="logo-solid"/>
</svg>
</a>
<h1 class="pkg-full-name">
<a href="/roc/builtins">roc/builtins</a>
</h1>
<a class="version" href="/roc/builtins/1.0.0">1.0.0</a>
</div>
<label class="search-button" for="module-search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 480" width="18" height="18" aria-labelledby="magnifying-glass">
<title id="magnifying-glass">Search modules</title>
<path role="presentation" fill="none" stroke="var(--faded-color)" stroke-width="48" stroke-linecap="round" d="m280,278a153,153 0 1,0-2,2l170,170m-91-117 110,110-26,26-110-110"/>
</svg>
</label>
</nav>
</header>
<main>
<h2 class="module-name"><a href="#">Str</a></h2>
<p>Dealing with text is a deep topic, so by design, Roc&#39;s <code><a href="#">Str</a></code> module sticks
to the basics.
</p>
<p><i>For more advanced use cases like working with raw <a href="https://unicode.org/glossary/#code_point">code points</a>,
see the <a href="roc/unicode">roc/unicode</a> package. For locale-specific text
functions (including capitalizing a string, as capitalization rules vary by locale)
see the <a href="roc/locale">roc/locale</a> package.</i></p>
<h3 id="unicode">Unicode</h3>
<p>Unicode can represent text values which span multiple languages, symbols, and emoji.
Here are some valid Roc strings:</p>
<pre><code>&quot;Roc&quot;
&quot;&quot;
&quot;🐼&quot;</code></pre>
<p>Every Unicode string is a sequence of <a href="https://unicode.org/glossary/#grapheme_cluster">grapheme clusters</a>.
A grapheme cluster corresponds to what a person reading a string might call a &quot;character&quot;,
but because the term &quot;character&quot; is used to mean many different concepts across
different programming languages, the documentation for Roc strings intentionally
avoids it. Instead, we use the term &quot;clusters&quot; as a shorthand for &quot;grapheme clusters.&quot;</p>
<p>You can get the number of grapheme clusters in a string by calling <code><a href="#Str.countClusters">Str.countClusters</a></code> on it:</p>
<pre><code>Str.countClusters &quot;Roc!&quot; # 4
Str.countClusters &quot;折り紙&quot; # 3
Str.countClusters &quot;🐼&quot; # 1</code></pre>
<p>The <code>countClusters</code> function walks through the entire string to get its answer,
so if you want to check whether a string is empty, you&#39;ll get much better performance
by calling <code>Str.isEmpty myStr</code> instead of <code>Str.countClusters myStr == 0</code>.</p>
<h3 id="escape-sequences">Escape sequences</h3>
<p>If you put a <code>\</code> in a Roc string literal, it begins an <em>escape sequence</em>.
An escape sequence is a convenient way to insert certain strings into other strings.
For example, suppose you write this Roc string:</p>
<pre><code>"It wasn't a rock\nIt was a rock lobster!"</code></pre>
<p>The <code>&quot;\n&quot;</code> in the middle will insert a line break into this string. There are
other ways of getting a line break in there, but <code>&quot;\n&quot;</code> is the most common.</p>
<p>Another way you could insert a newline is by writing <code>\u{0x0A}</code> instead of <code>\n</code>.
That would result in the same string, because the <code>\u</code> escape sequence inserts
<a href="https://unicode.org/glossary/#code_point">Unicode code points</a> directly into
the string. The Unicode code point 10 is a newline, and 10 is <code>0A</code> in hexadecimal.
<code>0x0A</code> is a Roc hexadecimal literal, and <code>\u</code> escape sequences are always
followed by a hexadecimal literal inside <code>{</code> and <code>}</code> like this.</p>
<p>As another example, <code>&quot;R\u{0x6F}c&quot;</code> is the same string as <code>&quot;Roc&quot;</code>, because
<code>&quot;\u{0x6F}&quot;</code> corresponds to the Unicode code point for lowercase <code>o</code>. If you
want to <a href="https://en.wikipedia.org/wiki/Metal_umlaut">spice things up a bit</a>,
you can write <code>&quot;R\u{0xF6}c&quot;</code> as an alternative way to get the string <code>&quot;Röc&quot;</code>.</p>
<p>Roc strings also support these escape sequences:</p>
<ul>
<li><code>\\</code> - an actual backslash (writing a single <code>\</code> always begins an escape sequence!)</li>
<li><code>\&quot;</code> - an actual quotation mark (writing a <code>&quot;</code> without a <code>\</code> ends the string)</li>
<li><code>\r</code> - <a href="https://en.wikipedia.org/wiki/Carriage_Return">carriage return</a></li>
<li><code>\t</code> - <a href="https://en.wikipedia.org/wiki/Tab_key#Tab_characters">horizontal tab</a></li>
<li><code>\v</code> - <a href="https://en.wikipedia.org/wiki/Tab_key#Tab_characters">vertical tab</a></li>
</ul>
<p>You can also use escape sequences to insert named strings into other strings, like so:</p>
<pre><code>name = "Lee"
city = "Roctown"
greeting = "Hello, \(name)! Welcome to \(city)."</code></pre>
<p>Here, <code>greeting</code> will become the string <code>&quot;Hello, Lee! Welcome to Roctown.&quot;</code>.
This is known as <a href="https://en.wikipedia.org/wiki/String_interpolation">string interpolation</a>,
and you can use it as many times as you like inside a string. The name
between the parentheses must refer to a <code>Str</code> value that is currently in
scope, and it must be a name - it can&#39;t be an arbitrary expression like a function call.</p>
<h3 id="encoding">Encoding</h3>
<p>Roc strings are not coupled to any particular
<a href="https://en.wikipedia.org/wiki/Character_encoding">encoding</a>. As it happens,
they are currently encoded in UTF-8, but this module is intentionally designed
not to rely on that implementation detail so that a future release of Roc can
potentially change it without breaking existing Roc applications. (UTF-8
seems pretty great today, but so did UTF-16 at an earlier point in history.)</p>
<p>This module has functions to can convert a <code><a href="#Str">Str</a></code> to a <code><a href="#List">List</a></code> of raw <a href="https://unicode.org/glossary/#code_unit">code unit</a>
integers (not to be confused with the <a href="https://unicode.org/glossary/#code_point">code points</a>
mentioned earlier) in a particular encoding. If you need encoding-specific functions,
you should take a look at the <a href="roc/unicode">roc/unicode</a> package.
It has many more tools than this module does!
<h3 id="types">Types</h3>
<code class="code-snippet"><a id="Str.Str" class="type-def" href="#Str.Str">Str</a></code>
<p>A <a href="https://unicode.org">Unicode</a> text value.</p>
</main>
</div>
</div>
<footer>
<p>Made by people who like to make nice things.</p>
<p>© 2020-present</p></footer>
</div>
</body>
</html>

View file

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="240" height="240" viewBox="0 0 51 53" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="dark-gradient" x2="0.8" y2="1">
<stop offset="0%" stop-color="#ffffff"/>
<stop offset="50%" stop-color="#7733ff"/>
<stop offset="80%" stop-color="#2B0080"/>
</linearGradient>
<linearGradient id="light-gradient" x2="0.35" y2="1">
<stop offset="0%" stop-color="#ffffff"/>
<stop offset="100%" stop-color="#5c0bff" />
</linearGradient>
</defs>
<path d="M23.6751 22.7086L17.655 53L27.4527 45.2132L26.4673 39.3424L23.6751 22.7086Z" fill="url(#dark-gradient)"/>
<path d="M37.2438 19.0101L44.0315 26.3689L45 22L45.9665 16.6324L37.2438 19.0101Z" fill="url(#light-gradient)"/>
<path d="M23.8834 3.21052L0 0L23.6751 22.7086L23.8834 3.21052Z" fill="url(#light-gradient)"/>
<path d="M44.0315 26.3689L23.6751 22.7086L26.4673 39.3424L44.0315 26.3689Z" fill="url(#light-gradient)"/>
<path d="M50.5 22L45.9665 16.6324L45 22H50.5Z" fill="url(#dark-gradient)"/>
<path d="M23.6751 22.7086L44.0315 26.3689L37.2438 19.0101L23.8834 3.21052L23.6751 22.7086Z" fill="url(#dark-gradient)"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -1,399 +0,0 @@
:root {
--link-color: #5c0bff;
--text-color: #333333;
--logo-solid: #aaaaaa;
--code-color: #222222;
--main-bg-color: #fdfdfd;
--border-bg-color: #E9E9E9;
--code-bg-color: #eeeeee;
--logo-gradient-start: #aaaaaa;
--logo-gradient-mid: #777777;
--logo-gradient-end: #333333;
--faded-color: #4C4C4C;
--monospace-font;
--font-sans: -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif;
--font-mono: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;
--sidebar-width: 240px;
}
a {
color: #972395;
}
.top-header {
box-sizing: border-box;
flex-direction: row;
align-items: center;
display: flex;
font-family: var(--font-sans);
font-size: 24px;
}
.main-nav {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: nowrap;
flex-grow: 1;
box-sizing: border-box;
padding: 6px 0;
min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */
}
.logo {
padding-left: 16px;
padding-right: 8px;
}
.logo svg {
height: 48px;
width: 48px;
}
.logo:hover {
text-decoration: none;
}
.logo-solid {
fill: url("#logo-gradient")
}
.logo:hover .logo-solid {
fill: var(--link-color);
}
.pkg-full-name {
color: var(--text-color);
display: inline-block;
font-size: 32px;
margin: 0 18px;
font-weight: normal;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-bottom: 8px;
}
.pkg-full-name a {
color: inherit;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.pkg-and-logo {
min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */
display: flex;
align-items: flex-end;
height: 55px;
}
.main-container {
min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */
}
.search-button {
flex-shrink: 0; /* always shrink the package name before these; they have a relatively constrained length */
padding: 12px 18px;
margin-right: 42px;
display: none; /* only show this in the mobile view */
}
.version {
font-weight: bold;
padding: 10px;
margin-right: 8px;
color: var(--faded-color);
}
.container {
box-sizing: border-box;
display: flex;
flex-direction: column;
margin: 0 auto;
min-width: 780px;
max-width: 1280px;
}
body {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: var(--font-sans);
color: var(--text-color);
background-color: var(--border-bg-color);
}
p {
overflow-wrap: break-word;
margin: 24px 0;
}
footer {
font-size: 14px;
box-sizing: border-box;
padding: 16px;
}
footer p {
display: inline-block;
margin-top: 0;
margin-bottom: 8px;
}
.content {
box-sizing: border-box;
display: flex;
flex-direction: row-reverse; /* We want the sidebar in the DOM first for tab ordering, but it should render on the right. */
justify-content: space-between;
}
main {
box-sizing: border-box;
position: relative;
font-size: 18px;
line-height: 1.85em;
padding: 32px;
background-color: var(--main-bg-color);
clip-path: polygon(0 100%, 100% 100%, 100% 48px, calc(100% - 48px) 0, 0px 0px);
}
.sidebar {
position: relative;
box-sizing: border-box;
padding-left: 20px;
width: var(--sidebar-width);
margin-top: 128px;
flex-shrink: 0;
}
#sidebar-heading {
font-size: 32px;
font-weight: normal;
margin: 0;
padding: 0;
color: var(--faded-color);
font-family: var(--font-sans);
cursor: default;
}
.module-name {
font-size: 56px;
line-height: 1em;
font-family: var(--font-mono);
font-weight: bold;
margin-top: 18px;
margin-bottom: 48px;
}
.module-name a:visited {
color: var(--link-color);
}
.sidebar-link {
box-sizing: border-box;
font-size: 18px;
font-family: var(--font-mono);
font-weight: bold;
color: var(--faded-color);
display: block;
width: 100%;
padding: 8px 16px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
a {
color: var(--link-color);
}
a:visited {
color: inherit;
}
h3 {
font-size: 32px;
margin: 48px 0 24px 0;
}
h4 {
font-size: 24px;
}
.type-def {
font-size: 24px;
color: var(--link-color);
}
.code-snippet {
padding: 12px 16px;
display: block;
box-sizing: border-box;
font-family: var(--font-mono);
background-color: var(--border-bg-color);
}
code {
font-family: var(--font-mono);
color: var(--code-color);
background-color: var(--code-bg-color);
padding: 0 8px;
display: inline-block;
}
code a {
color: var(--link-color);
}
code a:visited {
color: var(--link-color);
}
pre {
margin: 36px 0;
padding: 8px;
box-sizing: border-box;
background-color: var(--code-bg-color);
overflow-x: auto;
}
#module-search {
width: 100%;
font-family: var(--font-mono);
display: block;
box-sizing: border-box;
font-size: 18px;
margin: 12px 0;
padding: 12px 16px;
border: none;
color: var(--faded-color);
background: none;
}
#module-search::placeholder {
color: var(--faded-color);
opacity: 1;
}
#module-search:hover {
background-color: var(--main-bg-color);
}
#module-search:focus {
background-color: var(--main-bg-color);
}
@media (prefers-color-scheme: dark) {
:root {
--main-bg-color: #363636;
--border-bg-color: #111111;
--code-color: #eeeeee;
--code-bg-color: #2b2b2b;
--text-color: #cccccc;
--logo-solid: #777777;
--faded-color: #bbbbbb;
--link-color: #b894ff;
--logo-gradient-start: #333333;
--logo-gradient-mid: #777777;
--logo-gradient-end: #aaaaaa;
}
html {
scrollbar-color: #444444 #2f2f2f;
}
}
@media only screen and (max-device-width: 480px) {
.search-button {
display: block; /* This is only visible in mobile. */
}
.top-header {
width: auto;
}
.pkg-full-name {
margin-left: 8px;
margin-right: 12px;
font-size: 24px;
padding-bottom: 14px;
}
.pkg-full-name a {
vertical-align: middle;
padding: 18px 0;
}
.logo {
padding-left: 2px;
width: 50px;
height: 54px;
}
.version {
margin: 0;
font-weight: normal;
font-size: 18px;
padding-bottom: 16px;
}
.module-name {
font-size: 36px;
margin-top: 8px;
margin-bottom: 8px;
max-width: calc(100% - 18px);
overflow: hidden;
text-overflow: ellipsis;
}
main {
padding: 18px;
font-size: 16px;
}
.container {
margin: 0;
min-width: 320px;
max-width: 100%;
}
.content {
flex-direction: column;
}
.sidebar {
margin-top: 0;
padding-left: 0;
width: auto;
}
#sidebar-heading {
font-size: 24px;
margin: 16px;
}
h3 {
font-size: 18px;
margin: 0;
padding: 0;
}
h4 {
font-size: 16px;
}
.main-nav {
justify-content: space-between;
}
.content {
/* Display the sidebar below <main> without affecting tab index */
flex-direction: column-reverse;
}
}