mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 14:54:47 +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-rc = "14" # im and im-rc should always have the same version!
|
||||
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"] }
|
||||
# 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
|
||||
|
||||
// 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
|
||||
add_type(
|
||||
Symbol::FLOAT_DIV,
|
||||
|
@ -361,6 +373,24 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
|||
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
|
||||
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
|
||||
add_type(
|
||||
Symbol::LIST_LEN,
|
||||
|
|
|
@ -415,6 +415,18 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
|||
|
||||
// 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
|
||||
add_type(
|
||||
Symbol::FLOAT_DIV,
|
||||
|
@ -451,6 +463,24 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
|||
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
|
||||
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))
|
||||
// , Attr (u | v) 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-rc = "14" # im and im-rc should always have the same version!
|
||||
bumpalo = { version = "3.2", features = ["collections"] }
|
||||
inlinable_string = "0.1.0"
|
||||
inlinable_string = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1"
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::def::Def;
|
||||
use crate::expr::{Expr, Recursive};
|
||||
use roc_collections::all::{MutMap, SendMap};
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_module::ident::TagName;
|
||||
use roc_module::low_level::LowLevel;
|
||||
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
|
||||
/// 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.
|
||||
pub fn builtin_defs(var_store: &VarStore) -> MutMap<Symbol, Def> {
|
||||
pub fn builtin_defs(var_store: &VarStore) -> MutMap<Symbol, Expr> {
|
||||
mut_map! {
|
||||
Symbol::LIST_LEN => list_len(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_REM => int_rem(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
|
||||
fn int_is_odd(var_store: &VarStore) -> Def {
|
||||
fn int_is_odd(var_store: &VarStore) -> Expr {
|
||||
use crate::expr::Expr::*;
|
||||
|
||||
defn(
|
||||
|
@ -62,7 +200,7 @@ fn int_is_odd(var_store: &VarStore) -> Def {
|
|||
}
|
||||
|
||||
/// Int.isEven : Int -> Bool
|
||||
fn int_is_even(var_store: &VarStore) -> Def {
|
||||
fn int_is_even(var_store: &VarStore) -> Expr {
|
||||
use crate::expr::Expr::*;
|
||||
|
||||
defn(
|
||||
|
@ -85,7 +223,7 @@ fn int_is_even(var_store: &VarStore) -> Def {
|
|||
}
|
||||
|
||||
/// List.len : List * -> Int
|
||||
fn list_len(var_store: &VarStore) -> Def {
|
||||
fn list_len(var_store: &VarStore) -> Expr {
|
||||
use crate::expr::Expr::*;
|
||||
|
||||
// Polymorphic wrapper around LowLevel::ListLen
|
||||
|
@ -100,7 +238,7 @@ fn list_len(var_store: &VarStore) -> Def {
|
|||
}
|
||||
|
||||
/// 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::*;
|
||||
|
||||
defn(
|
||||
|
@ -169,7 +307,7 @@ fn list_get(var_store: &VarStore) -> Def {
|
|||
}
|
||||
|
||||
/// Int.rem : Int, Int -> Int
|
||||
fn int_rem(var_store: &VarStore) -> Def {
|
||||
fn int_rem(var_store: &VarStore) -> Expr {
|
||||
use crate::expr::Expr::*;
|
||||
|
||||
defn(
|
||||
|
@ -216,7 +354,7 @@ fn int_rem(var_store: &VarStore) -> Def {
|
|||
}
|
||||
|
||||
/// Int.abs : Int -> Int
|
||||
fn int_abs(var_store: &VarStore) -> Def {
|
||||
fn int_abs(var_store: &VarStore) -> Expr {
|
||||
use crate::expr::Expr::*;
|
||||
|
||||
defn(
|
||||
|
@ -253,7 +391,7 @@ fn int_abs(var_store: &VarStore) -> Def {
|
|||
}
|
||||
|
||||
/// 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::*;
|
||||
|
||||
defn(
|
||||
|
@ -312,7 +450,7 @@ fn int_div(var_store: &VarStore) -> Def {
|
|||
}
|
||||
|
||||
/// 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::*;
|
||||
|
||||
defn(
|
||||
|
@ -404,7 +542,7 @@ fn call(symbol: Symbol, args: Vec<Expr>, var_store: &VarStore) -> Expr {
|
|||
}
|
||||
|
||||
#[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::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))))
|
||||
.collect();
|
||||
|
||||
let expr = Closure(
|
||||
Closure(
|
||||
var_store.fresh(),
|
||||
fn_name,
|
||||
Recursive::NotRecursive,
|
||||
closure_args,
|
||||
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();
|
||||
|
||||
for symbol in can_defs_by_symbol.keys().into_iter() {
|
||||
defined_symbols.push(symbol.clone());
|
||||
defined_symbols_set.insert(symbol.clone());
|
||||
defined_symbols.push(*symbol);
|
||||
defined_symbols_set.insert(*symbol);
|
||||
}
|
||||
|
||||
// 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::*;
|
||||
match pattern {
|
||||
Identifier(symbol) => {
|
||||
vars_by_symbol.insert(symbol.clone(), expr_var);
|
||||
vars_by_symbol.insert(*symbol, expr_var);
|
||||
}
|
||||
|
||||
AppliedTag { arguments, .. } => {
|
||||
|
@ -701,7 +701,7 @@ fn pattern_to_vars_by_symbol(
|
|||
|
||||
RecordDestructure { 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_can_pattern.value,
|
||||
&loc_can_expr.value.clone(),
|
||||
&loc_can_expr.value,
|
||||
) {
|
||||
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
|
||||
// case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...`
|
||||
// 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).
|
||||
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
|
||||
// would result in circular def errors!)
|
||||
refs_by_symbol
|
||||
.entry(defined_symbol.clone())
|
||||
.entry(*defined_symbol)
|
||||
.and_modify(|(_, refs)| {
|
||||
refs.lookups = refs.lookups.without(defined_symbol);
|
||||
});
|
||||
|
@ -1010,7 +1010,7 @@ fn canonicalize_pending_def<'a>(
|
|||
};
|
||||
|
||||
refs_by_symbol.insert(
|
||||
symbol.clone(),
|
||||
*symbol,
|
||||
(
|
||||
Located {
|
||||
value: ident.clone(),
|
||||
|
@ -1057,7 +1057,7 @@ fn canonicalize_pending_def<'a>(
|
|||
env.tailcallable_symbol = Some(*defined_symbol);
|
||||
|
||||
// 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) =
|
||||
|
@ -1082,7 +1082,7 @@ fn canonicalize_pending_def<'a>(
|
|||
) = (
|
||||
&loc_pattern.value,
|
||||
&loc_can_pattern.value,
|
||||
&loc_can_expr.value.clone(),
|
||||
&loc_can_expr.value,
|
||||
) {
|
||||
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
|
||||
// case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...`
|
||||
// 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).
|
||||
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
|
||||
// would result in circular def errors!)
|
||||
refs_by_symbol
|
||||
.entry(defined_symbol.clone())
|
||||
.entry(*defined_symbol)
|
||||
.and_modify(|(_, refs)| {
|
||||
refs.lookups = refs.lookups.without(defined_symbol);
|
||||
});
|
||||
|
@ -1147,7 +1147,7 @@ fn canonicalize_pending_def<'a>(
|
|||
});
|
||||
|
||||
refs_by_symbol.insert(
|
||||
symbol.clone(),
|
||||
symbol,
|
||||
(
|
||||
Located {
|
||||
value: ident.clone().into(),
|
||||
|
@ -1265,7 +1265,7 @@ fn closure_recursivity(symbol: Symbol, closures: &MutMap<Symbol, References>) ->
|
|||
|
||||
if let Some(references) = closures.get(&symbol) {
|
||||
for v in &references.calls {
|
||||
stack.push(v.clone());
|
||||
stack.push(*v);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// add its called to the stack
|
||||
for v in &nested_references.calls {
|
||||
stack.push(v.clone());
|
||||
stack.push(*v);
|
||||
}
|
||||
}
|
||||
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(
|
||||
|
@ -820,7 +820,7 @@ where
|
|||
answer = answer.union(other_refs);
|
||||
}
|
||||
|
||||
answer.lookups.insert(local.clone());
|
||||
answer.lookups.insert(*local);
|
||||
}
|
||||
|
||||
for call in refs.calls.iter() {
|
||||
|
@ -830,7 +830,7 @@ where
|
|||
answer = answer.union(other_refs);
|
||||
}
|
||||
|
||||
answer.calls.insert(call.clone());
|
||||
answer.calls.insert(*call);
|
||||
}
|
||||
|
||||
answer
|
||||
|
@ -862,7 +862,7 @@ where
|
|||
answer = answer.union(other_refs);
|
||||
}
|
||||
|
||||
answer.lookups.insert(closed_over_local.clone());
|
||||
answer.lookups.insert(*closed_over_local);
|
||||
}
|
||||
|
||||
for call in references.calls.iter() {
|
||||
|
|
|
@ -57,7 +57,7 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
|
|||
|
||||
match pattern {
|
||||
Identifier(symbol) => {
|
||||
symbols.push(symbol.clone());
|
||||
symbols.push(*symbol);
|
||||
}
|
||||
|
||||
AppliedTag { arguments, .. } => {
|
||||
|
@ -67,7 +67,7 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
|
|||
}
|
||||
RecordDestructure { 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 {
|
||||
match pattern {
|
||||
Identifier(symbol) => {
|
||||
headers.insert(symbol.clone(), annotation.clone());
|
||||
headers.insert(*symbol, annotation.clone());
|
||||
true
|
||||
}
|
||||
Underscore
|
||||
|
@ -64,7 +64,7 @@ fn headers_from_annotation_help(
|
|||
// NOTE ignores the .guard field.
|
||||
if let Some(field_type) = fields.get(&destruct.value.label) {
|
||||
headers.insert(
|
||||
destruct.value.symbol.clone(),
|
||||
destruct.value.symbol,
|
||||
Located::at(annotation.region, field_type.clone()),
|
||||
);
|
||||
} else {
|
||||
|
@ -123,7 +123,7 @@ pub fn constrain_pattern(
|
|||
|
||||
Identifier(symbol) => {
|
||||
state.headers.insert(
|
||||
symbol.clone(),
|
||||
*symbol,
|
||||
Located {
|
||||
region,
|
||||
value: expected.get_type(),
|
||||
|
@ -197,7 +197,7 @@ pub fn constrain_pattern(
|
|||
if !state.headers.contains_key(&symbol) {
|
||||
state
|
||||
.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());
|
||||
|
|
|
@ -148,7 +148,7 @@ fn constrain_pattern(
|
|||
match &pattern.value {
|
||||
Identifier(symbol) => {
|
||||
state.headers.insert(
|
||||
symbol.clone(),
|
||||
*symbol,
|
||||
Located {
|
||||
region: pattern.region,
|
||||
value: expected.get_type(),
|
||||
|
@ -223,10 +223,9 @@ fn constrain_pattern(
|
|||
let expected = PExpected::NoExpectation(pat_type.clone());
|
||||
|
||||
if !state.headers.contains_key(&symbol) {
|
||||
state.headers.insert(
|
||||
symbol.clone(),
|
||||
Located::at(pattern.region, pat_type.clone()),
|
||||
);
|
||||
state
|
||||
.headers
|
||||
.insert(*symbol, Located::at(pattern.region, pat_type.clone()));
|
||||
}
|
||||
|
||||
field_types.insert(label.clone(), pat_type.clone());
|
||||
|
@ -1396,7 +1395,7 @@ fn constrain_var(
|
|||
Lookup(symbol_for_lookup, expected, region)
|
||||
}
|
||||
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 (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-rc = "14" # im and im-rc should always have the same version!
|
||||
bumpalo = { version = "3.2", features = ["collections"] }
|
||||
inlinable_string = "0.1.0"
|
||||
inlinable_string = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
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-rc = "14" # im and im-rc should always have the same version!
|
||||
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.
|
||||
#
|
||||
# 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,
|
||||
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_LROUND_I64_F64: &str = "llvm.lround.i64.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>(
|
||||
module: &Module<'ctx>,
|
||||
|
@ -422,7 +436,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
|||
if elems.is_empty() {
|
||||
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
|
||||
BasicValueEnum::StructValue(struct_type.const_zero())
|
||||
} else {
|
||||
|
@ -1203,6 +1217,8 @@ fn call_with_args<'a, 'ctx, 'env>(
|
|||
|
||||
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 => {
|
||||
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::LIST_SET => list_set(parent, args, env, InPlace::Clone),
|
||||
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 => {
|
||||
debug_assert!(args.len() == 2);
|
||||
|
||||
|
|
|
@ -95,6 +95,7 @@ mod gen_builtins {
|
|||
|
||||
#[test]
|
||||
fn gen_add_f64() {
|
||||
with_larger_debug_stack(|| {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
|
@ -104,6 +105,7 @@ mod gen_builtins {
|
|||
6.5,
|
||||
f64
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -147,6 +149,7 @@ mod gen_builtins {
|
|||
|
||||
#[test]
|
||||
fn gen_add_i64() {
|
||||
with_larger_debug_stack(|| {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
|
@ -156,6 +159,7 @@ mod gen_builtins {
|
|||
6,
|
||||
i64
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn gen_is_odd() {
|
||||
assert_evals_to!("Int.isOdd 4", false, bool);
|
||||
|
@ -269,6 +315,24 @@ mod gen_builtins {
|
|||
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]
|
||||
fn lt_i64() {
|
||||
assert_evals_to!("1 < 2", true, bool);
|
||||
|
@ -387,6 +451,7 @@ mod gen_builtins {
|
|||
}
|
||||
#[test]
|
||||
fn tail_call_elimination() {
|
||||
with_larger_debug_stack(|| {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
|
@ -401,6 +466,7 @@ mod gen_builtins {
|
|||
500000500000,
|
||||
i64
|
||||
);
|
||||
})
|
||||
}
|
||||
#[test]
|
||||
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]
|
||||
fn empty_list_len() {
|
||||
with_larger_debug_stack(|| {
|
||||
assert_evals_to!("List.len []", 0, usize);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_int_list_len() {
|
||||
with_larger_debug_stack(|| {
|
||||
assert_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, usize);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -472,11 +553,14 @@ mod gen_builtins {
|
|||
|
||||
#[test]
|
||||
fn empty_list_is_empty() {
|
||||
with_larger_debug_stack(|| {
|
||||
assert_evals_to!("List.isEmpty []", true, bool);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_int_list() {
|
||||
with_larger_debug_stack(|| {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
|
@ -488,10 +572,12 @@ mod gen_builtins {
|
|||
12,
|
||||
i64
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_empty_list() {
|
||||
with_larger_debug_stack(|| {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
|
@ -503,6 +589,7 @@ mod gen_builtins {
|
|||
-1,
|
||||
i64
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -13,7 +13,7 @@ mod helpers;
|
|||
|
||||
#[cfg(test)]
|
||||
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 inkwell::context::Context;
|
||||
use inkwell::execution_engine::JitFunction;
|
||||
|
@ -406,6 +406,7 @@ mod gen_primitives {
|
|||
|
||||
#[test]
|
||||
fn gen_chained_defs() {
|
||||
with_larger_debug_stack(|| {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
|
@ -421,9 +422,11 @@ mod gen_primitives {
|
|||
1337,
|
||||
i64
|
||||
);
|
||||
})
|
||||
}
|
||||
#[test]
|
||||
fn gen_nested_defs() {
|
||||
with_larger_debug_stack(|| {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
|
@ -461,5 +464,6 @@ mod gen_primitives {
|
|||
1337,
|
||||
i64
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ mod helpers;
|
|||
|
||||
#[cfg(test)]
|
||||
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 inkwell::context::Context;
|
||||
use inkwell::execution_engine::JitFunction;
|
||||
|
@ -213,6 +213,7 @@ mod gen_tags {
|
|||
|
||||
#[test]
|
||||
fn even_odd() {
|
||||
with_larger_debug_stack(|| {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
|
@ -234,6 +235,7 @@ mod gen_tags {
|
|||
true,
|
||||
bool
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -6,10 +6,12 @@ pub mod eval;
|
|||
use self::bumpalo::Bump;
|
||||
use roc_builtins::unique::uniq_stdlib;
|
||||
use roc_can::constraint::Constraint;
|
||||
use roc_can::def::Def;
|
||||
use roc_can::env::Env;
|
||||
use roc_can::expected::Expected;
|
||||
use roc_can::expr::{canonicalize_expr, Expr, Output};
|
||||
use roc_can::operator;
|
||||
use roc_can::pattern::Pattern;
|
||||
use roc_can::scope::Scope;
|
||||
use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet};
|
||||
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.
|
||||
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(
|
||||
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 {
|
||||
region: Region::zero(),
|
||||
value: with_builtins,
|
||||
|
@ -256,6 +272,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
|
|||
SendMap::default(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let loc_expr = Located {
|
||||
region: loc_expr.region,
|
||||
|
|
|
@ -18,7 +18,7 @@ roc_unify = { path = "../unify" }
|
|||
roc_parse = { path = "../parse" }
|
||||
roc_solve = { path = "../solve" }
|
||||
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"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -9,7 +9,7 @@ license = "Apache-2.0"
|
|||
roc_region = { path = "../region" }
|
||||
roc_collections = { path = "../collections" }
|
||||
bumpalo = { version = "3.2", features = ["collections"] }
|
||||
inlinable_string = "0.1.0"
|
||||
inlinable_string = "0.1"
|
||||
lazy_static = "1.4"
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -623,6 +623,12 @@ define_builtins! {
|
|||
29 INT_IS_ODD_ARG: "isOdd#arg"
|
||||
30 INT_IS_EVEN: "isEven"
|
||||
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" => {
|
||||
0 FLOAT_FLOAT: "Float" imported // the Float.Float type alias
|
||||
|
@ -635,13 +641,23 @@ define_builtins! {
|
|||
7 FLOAT_LOWEST: "lowest"
|
||||
8 FLOAT_ADD: "#add"
|
||||
9 FLOAT_SUB: "#sub"
|
||||
10 FLOAT_EQ: "#eq"
|
||||
10 FLOAT_EQ: "eq"
|
||||
11 FLOAT_ROUND: "round"
|
||||
12 FLOAT_LT: "#lt"
|
||||
13 FLOAT_LTE: "#lte"
|
||||
14 FLOAT_GT: "#gt"
|
||||
14 FLOAT_GT: "gt"
|
||||
15 FLOAT_GTE: "#gte"
|
||||
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" => {
|
||||
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
|
||||
|
@ -676,6 +692,7 @@ define_builtins! {
|
|||
15 LIST_CONCAT: "concat"
|
||||
16 LIST_FIRST: "first"
|
||||
17 LIST_FIRST_ARG: "first#list"
|
||||
18 LIST_SINGLE: "single"
|
||||
}
|
||||
7 RESULT: "Result" => {
|
||||
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
|
||||
|
|
|
@ -10,7 +10,7 @@ roc_collections = { path = "../collections" }
|
|||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
bumpalo = { version = "3.2", features = ["collections"] }
|
||||
inlinable_string = "0.1.0"
|
||||
inlinable_string = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1"
|
||||
|
|
|
@ -10,7 +10,7 @@ roc_collections = { path = "../collections" }
|
|||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_parse = { path = "../parse" }
|
||||
inlinable_string = "0.1.0"
|
||||
inlinable_string = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1"
|
||||
|
|
|
@ -17,7 +17,7 @@ roc_can = { path = "../can" }
|
|||
roc_solve = { path = "../solve" }
|
||||
roc_mono = { path = "../mono" }
|
||||
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-rc = "14" # im and im-rc should always have the same version!
|
||||
distance = "0.4.0"
|
||||
|
|
|
@ -280,7 +280,7 @@ fn pretty_runtime_error<'b>(
|
|||
if idents.is_empty() {
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.ident(first.value.clone()))
|
||||
.append(alloc.ident(first.value))
|
||||
.append(alloc.reflow(
|
||||
" 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);
|
||||
|
||||
local_def_vars.insert(
|
||||
symbol.clone(),
|
||||
*symbol,
|
||||
Located {
|
||||
value: var,
|
||||
region: loc_type.region,
|
||||
|
@ -344,7 +344,7 @@ fn solve(
|
|||
let mut new_env = env.clone();
|
||||
for (symbol, loc_var) in local_def_vars.iter() {
|
||||
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);
|
||||
|
||||
local_def_vars.insert(
|
||||
symbol.clone(),
|
||||
*symbol,
|
||||
Located {
|
||||
value: var,
|
||||
region: loc_type.region,
|
||||
|
@ -469,7 +469,7 @@ fn solve(
|
|||
|
||||
for (symbol, loc_var) in local_def_vars.iter() {
|
||||
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]
|
||||
fn empty_list_literal() {
|
||||
with_larger_debug_stack(|| {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
|
@ -124,6 +125,7 @@ mod test_uniq_solve {
|
|||
),
|
||||
"Attr * (List *)",
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -12,7 +12,7 @@ roc_module = { path = "../module" }
|
|||
roc_parse = { path = "../parse" }
|
||||
roc_problem = { path = "../problem" }
|
||||
ven_ena = { path = "../../vendor/ena" }
|
||||
inlinable_string = "0.1.0"
|
||||
inlinable_string = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1"
|
||||
|
|
|
@ -74,7 +74,7 @@ impl Bool {
|
|||
|
||||
for atom in &self.1 {
|
||||
if let Variable(v) = atom {
|
||||
result.insert(v.clone());
|
||||
result.insert(*v);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,7 +152,7 @@ impl Bool {
|
|||
if let Variable(v) = atom {
|
||||
new_bound.insert(Variable(f(*v)));
|
||||
} else {
|
||||
new_bound.insert(atom.clone());
|
||||
new_bound.insert(*atom);
|
||||
}
|
||||
}
|
||||
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-rc = "14" # im and im-rc should always have the same version!
|
||||
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"] }
|
||||
# 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" }
|
||||
target-lexicon = "0.10"
|
||||
winit = "0.22"
|
||||
image = "0.23"
|
||||
gfx-hal = "0.5"
|
||||
glsl-to-spirv = "0.1"
|
||||
bincode = "1.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies.backend]
|
||||
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"
|
||||
wgpu = "0.5"
|
||||
glyph_brush = "0.7"
|
||||
log = "0.4"
|
||||
zerocopy = "0.3"
|
||||
env_logger = "0.7"
|
||||
futures = "0.3"
|
||||
wgpu_glyph = "0.9"
|
||||
|
||||
[dev-dependencies]
|
||||
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::{
|
||||
device::Device,
|
||||
window::{Extent2D, PresentationSurface, Surface},
|
||||
Instance,
|
||||
};
|
||||
use glsl_to_spirv::ShaderType;
|
||||
#![warn(clippy::all, clippy::dbg_macro)]
|
||||
// I'm skeptical that clippy:large_enum_variant is a good lint to have globally enabled.
|
||||
//
|
||||
// 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
|
||||
// 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::mem::ManuallyDrop;
|
||||
use std::path::Path;
|
||||
use wgpu_glyph::{ab_glyph, GlyphBrushBuilder, Section, Text};
|
||||
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,
|
||||
/// or if you provide it 1 or more files or directories to open on launch.
|
||||
pub fn launch(_filepaths: &[&Path]) -> io::Result<()> {
|
||||
// TODO do any initialization here
|
||||
|
||||
run_event_loop();
|
||||
run_event_loop().expect("Error running event loop");
|
||||
|
||||
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> {
|
||||
instance: B::Instance,
|
||||
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,
|
||||
}
|
||||
// Open window and create a surface
|
||||
let event_loop = winit::event_loop::EventLoop::new();
|
||||
|
||||
struct ResourceHolder<B: gfx_hal::Backend>(ManuallyDrop<Resources<B>>);
|
||||
|
||||
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)
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut should_configure_swapchain = true;
|
||||
let surface = wgpu::Surface::create(&window);
|
||||
|
||||
let (instance, surface, adapter) = {
|
||||
let instance = backend::Instance::create("roc_editor", 1).expect("Backend not supported");
|
||||
|
||||
let surface = unsafe {
|
||||
instance
|
||||
.create_surface(&window)
|
||||
.expect("Failed to create surface for window")
|
||||
};
|
||||
|
||||
let adapter = instance.enumerate_adapters().remove(0);
|
||||
|
||||
(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;
|
||||
// Initialize GPU
|
||||
let (device, queue) = futures::executor::block_on(async {
|
||||
let adapter = wgpu::Adapter::request(
|
||||
&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||
compatible_surface: Some(&surface),
|
||||
},
|
||||
wgpu::BackendBit::all(),
|
||||
)
|
||||
.await
|
||||
.expect("Request adapter");
|
||||
|
||||
adapter
|
||||
.physical_device
|
||||
.open(&[(queue_family, &[1.0])], gfx_hal::Features::empty())
|
||||
.expect("Failed to open device")
|
||||
};
|
||||
|
||||
(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(),
|
||||
.request_device(&wgpu::DeviceDescriptor {
|
||||
extensions: wgpu::Extensions {
|
||||
anisotropic_filtering: false,
|
||||
},
|
||||
EntryPoint {
|
||||
entry: "main",
|
||||
module: &fragment_shader_module,
|
||||
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),
|
||||
limits: wgpu::Limits { max_bind_groups: 1 },
|
||||
})
|
||||
.await
|
||||
});
|
||||
|
||||
let pipeline = device
|
||||
.create_graphics_pipeline(&pipeline_desc, None)
|
||||
.expect("Failed to create graphics pipeline");
|
||||
// Prepare swap chain
|
||||
let render_format = wgpu::TextureFormat::Bgra8UnormSrgb;
|
||||
let mut size = window.inner_size();
|
||||
|
||||
device.destroy_shader_module(vertex_shader_module);
|
||||
device.destroy_shader_module(fragment_shader_module);
|
||||
let mut 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,
|
||||
},
|
||||
);
|
||||
|
||||
pipeline
|
||||
};
|
||||
// Prepare glyph_brush
|
||||
let inconsolata =
|
||||
ab_glyph::FontArc::try_from_slice(include_bytes!("../Inconsolata-Regular.ttf"))?;
|
||||
|
||||
let pipeline = unsafe {
|
||||
make_pipeline::<backend::Backend>(
|
||||
&device,
|
||||
&render_pass,
|
||||
&pipeline_layout,
|
||||
vertex_shader,
|
||||
fragment_shader,
|
||||
)
|
||||
};
|
||||
let mut glyph_brush = GlyphBrushBuilder::using_font(inconsolata).build(&device, render_format);
|
||||
|
||||
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 mut text_state = String::new();
|
||||
let mut keyboard_modifiers = ModifiersState::empty();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
use winit::event::{Event, WindowEvent};
|
||||
use winit::event_loop::ControlFlow;
|
||||
// Render loop
|
||||
window.request_redraw();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
// TODO dynamically switch this on/off depending on whether any
|
||||
// animations are running. Should conserve CPU usage and battery life!
|
||||
if is_animating {
|
||||
|
@ -330,24 +103,56 @@ fn run_event_loop() {
|
|||
}
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: window_event,
|
||||
winit::event::Event::WindowEvent {
|
||||
event: winit::event::WindowEvent::CloseRequested,
|
||||
..
|
||||
} => match window_event {
|
||||
WindowEvent::CloseRequested => {
|
||||
println!("✈️ Thank you for flying Roc Airlines!");
|
||||
*control_flow = ControlFlow::Exit
|
||||
} => *control_flow = winit::event_loop::ControlFlow::Exit,
|
||||
winit::event::Event::WindowEvent {
|
||||
event: winit::event::WindowEvent::Resized(new_size),
|
||||
..
|
||||
} => {
|
||||
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) => {
|
||||
surface_extent = Extent2D {
|
||||
width: dims.width,
|
||||
height: dims.height,
|
||||
};
|
||||
should_configure_swapchain = true;
|
||||
winit::event::Event::WindowEvent {
|
||||
event: winit::event::WindowEvent::ReceivedCharacter(ch),
|
||||
..
|
||||
} => {
|
||||
match ch {
|
||||
'\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 {
|
||||
handle_text_input(
|
||||
handle_keydown(
|
||||
&mut text_state,
|
||||
input.state,
|
||||
virtual_keycode,
|
||||
|
@ -355,215 +160,74 @@ fn run_event_loop() {
|
|||
);
|
||||
}
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
||||
surface_extent = Extent2D {
|
||||
width: new_inner_size.width,
|
||||
height: new_inner_size.height,
|
||||
};
|
||||
should_configure_swapchain = true;
|
||||
}
|
||||
WindowEvent::ModifiersChanged(modifiers) => {
|
||||
winit::event::Event::WindowEvent {
|
||||
event: winit::event::WindowEvent::ModifiersChanged(modifiers),
|
||||
..
|
||||
} => {
|
||||
keyboard_modifiers = modifiers;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::MainEventsCleared => window.request_redraw(),
|
||||
Event::RedrawRequested(_) => {
|
||||
let res: &mut Resources<_> = &mut resource_holder.0;
|
||||
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],
|
||||
}
|
||||
}
|
||||
winit::event::Event::MainEventsCleared => window.request_redraw(),
|
||||
winit::event::Event::RedrawRequested { .. } => {
|
||||
// Get a command encoder for the current frame
|
||||
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Redraw"),
|
||||
});
|
||||
|
||||
unsafe {
|
||||
use gfx_hal::pool::CommandPool;
|
||||
// Get the next frame
|
||||
let frame = swap_chain.get_next_texture().expect("Get next frame");
|
||||
|
||||
// We refuse to wait more than a second, to avoid hanging.
|
||||
let render_timeout_ns = 1_000_000_000;
|
||||
|
||||
res.device
|
||||
.wait_for_fence(&res.submission_complete_fence, render_timeout_ns)
|
||||
.expect("Out of memory or device lost");
|
||||
|
||||
res.device
|
||||
.reset_fence(&res.submission_complete_fence)
|
||||
.expect("Out of memory");
|
||||
|
||||
res.command_pool.reset(false);
|
||||
}
|
||||
|
||||
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],
|
||||
// Clear frame
|
||||
{
|
||||
let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
|
||||
attachment: &frame.view,
|
||||
resolve_target: None,
|
||||
load_op: wgpu::LoadOp::Clear,
|
||||
store_op: wgpu::StoreOp::Store,
|
||||
clear_color: wgpu::Color {
|
||||
r: 0.007,
|
||||
g: 0.007,
|
||||
b: 0.007,
|
||||
a: 1.0,
|
||||
},
|
||||
}],
|
||||
SubpassContents::Inline,
|
||||
);
|
||||
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);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
depth_stencil_attachment: None,
|
||||
});
|
||||
}
|
||||
|
||||
/// Compile some GLSL shader source to SPIR-V.
|
||||
/// TODO do this at build time - possibly in CI only
|
||||
fn compile_shader(glsl: &str, shader_type: ShaderType) -> Vec<u32> {
|
||||
use std::io::{Cursor, Read};
|
||||
glyph_brush.queue(Section {
|
||||
screen_position: (30.0, 30.0),
|
||||
bounds: (size.width as f32, size.height as f32),
|
||||
text: vec![Text::new("Enter some text:")
|
||||
.with_color([0.4666, 0.2, 1.0, 1.0])
|
||||
.with_scale(40.0)],
|
||||
..Section::default()
|
||||
});
|
||||
|
||||
let mut compiled_file =
|
||||
glsl_to_spirv::compile(glsl, shader_type).expect("Failed to compile shader");
|
||||
glyph_brush.queue(Section {
|
||||
screen_position: (30.0, 90.0),
|
||||
bounds: (size.width as f32, size.height as f32),
|
||||
text: vec![Text::new(format!("{}|", text_state).as_str())
|
||||
.with_color([1.0, 1.0, 1.0, 1.0])
|
||||
.with_scale(40.0)],
|
||||
..Section::default()
|
||||
});
|
||||
|
||||
let mut spirv_bytes = vec![];
|
||||
compiled_file.read_to_end(&mut spirv_bytes).unwrap();
|
||||
// Draw the text!
|
||||
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.
|
||||
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(
|
||||
fn handle_keydown(
|
||||
text_state: &mut String,
|
||||
elem_state: ElementState,
|
||||
virtual_keycode: VirtualKeyCode,
|
||||
|
@ -576,109 +240,12 @@ fn handle_text_input(
|
|||
}
|
||||
|
||||
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 => {
|
||||
// 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();
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
@ -688,5 +255,6 @@ fn handle_text_input(
|
|||
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