mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 23:04:49 +00:00
Merge remote-tracking branch 'origin/trunk' into specialize-separately
This commit is contained in:
commit
245a9fc951
40 changed files with 2118 additions and 1621 deletions
748
Cargo.lock
generated
748
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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.
|
||||||
#
|
#
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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) =
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
);
|
);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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.",
|
||||||
))
|
))
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -416,7 +416,7 @@ impl Composable for VarUsage {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.usage.insert(symbol.clone(), value);
|
self.usage.insert(*symbol, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
BIN
editor/Inconsolata-Regular.ttf
Normal file
BIN
editor/Inconsolata-Regular.ttf
Normal file
Binary file not shown.
BIN
editor/creature.png
Normal file
BIN
editor/creature.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 111 KiB |
704
editor/src/ast.rs
Normal file
704
editor/src/ast.rs
Normal 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);
|
||||||
|
}
|
|
@ -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
128
editor/src/text_state.rs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 |
145
www/index.html
145
www/index.html
|
@ -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'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>"Roc"
|
|
||||||
"鹏"
|
|
||||||
"🐼"</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 "character",
|
|
||||||
but because the term "character" is used to mean many different concepts across
|
|
||||||
different programming languages, the documentation for Roc strings intentionally
|
|
||||||
avoids it. Instead, we use the term "clusters" as a shorthand for "grapheme clusters."</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 "Roc!" # 4
|
|
||||||
Str.countClusters "折り紙" # 3
|
|
||||||
Str.countClusters "🐼" # 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'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>"\n"</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>"\n"</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>"R\u{0x6F}c"</code> is the same string as <code>"Roc"</code>, because
|
|
||||||
<code>"\u{0x6F}"</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>"R\u{0xF6}c"</code> as an alternative way to get the string <code>"Röc"</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>\"</code> - an actual quotation mark (writing a <code>"</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>"Hello, Lee! Welcome to Roctown."</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'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>
|
|
21
www/logo.svg
21
www/logo.svg
|
@ -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 |
399
www/styles.css
399
www/styles.css
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue