Prefer and and or for boolean operators

This commit is contained in:
Sam Mohr 2025-01-17 16:11:20 -08:00
parent d9d3fc74fc
commit a292e070d4
No known key found for this signature in database
GPG key ID: EA41D161A3C1BC99
35 changed files with 189 additions and 282 deletions

View file

@ -148,19 +148,19 @@ is_valid_char = \c ->
is_alpha_num : U8 -> Bool
is_alpha_num = \key ->
(key >= 48 && key <= 57) || (key >= 64 && key <= 90) || (key >= 97 && key <= 122)
(key >= 48 and key <= 57) or (key >= 64 and key <= 90) or (key >= 97 and key <= 122)
# Convert a base64 character/digit to its index
# See also [Wikipedia](https://en.wikipedia.org/wiki/Base64#Base64_table)
unsafe_convert_char : U8 -> U8
unsafe_convert_char = \key ->
if key >= 65 && key <= 90 then
if key >= 65 and key <= 90 then
# A-Z
key - 65
else if key >= 97 && key <= 122 then
else if key >= 97 and key <= 122 then
# a-z
(key - 97) + 26
else if key >= 48 && key <= 57 then
else if key >= 48 and key <= 57 then
# 0-9
(key - 48) + 26 + 26
else

View file

@ -50,7 +50,7 @@ safe = \queen, diagonal, xs ->
when xs is
Nil -> Bool.true
Cons(q, t) ->
if queen != q && queen != q + diagonal && queen != q - diagonal then
if queen != q and queen != q + diagonal and queen != q - diagonal then
safe(queen, (diagonal + 1), t)
else
Bool.false

View file

@ -19,10 +19,8 @@ to_str = \@Variable(char) ->
from_utf8 : U8 -> Result Variable [InvalidVariableUtf8]
from_utf8 = \char ->
if
char
>= 0x61 # "a"
&& char
<= 0x7A # "z"
char >= 0x61 # "a"
and char <= 0x7A # "z"
then
Ok(@Variable(char))
else

View file

@ -457,21 +457,15 @@ pop_variable = \ctx ->
is_digit : U8 -> Bool
is_digit = \char ->
char
>= 0x30 # `0`
&& char
<= 0x39 # `0`
char >= 0x30 # `0`
and char <= 0x39 # `0`
is_whitespace : U8 -> Bool
is_whitespace = \char ->
char
== 0xA # new line
|| char
== 0xD # carriage return
|| char
== 0x20 # space
|| char
== 0x9 # tab
char == 0xA # new line
or char == 0xD # carriage return
or char == 0x20 # space
or char == 0x9 # tab
end_unexpected = \err ->
when err is

View file

@ -1,4 +1,4 @@
module [Bool, Eq, true, false, and, or, not, is_eq, is_not_eq]
module [Bool, Eq, true, false, not, is_eq, is_not_eq]
## Defines a type that can be compared for total equality.
##
@ -44,55 +44,6 @@ true = @Bool(True)
false : Bool
false = @Bool(False)
## Returns `Bool.true` when both inputs are `Bool.true`. This is equivalent to
## the logic [AND](https://en.wikipedia.org/wiki/Logical_conjunction)
## gate. The infix operator `&&` can also be used as shorthand for
## `Bool.and`.
##
## ```roc
## expect Bool.and(Bool.true, Bool.true) == Bool.true
## expect (Bool.true && Bool.true) == Bool.true
## expect (Bool.false && Bool.true) == Bool.false
## expect (Bool.true && Bool.false) == Bool.false
## expect (Bool.false && Bool.false) == Bool.false
## ```
##
## ## Performance Details
##
## In Roc the `&&` and `||` work the same way as any
## other function. However, in some languages `&&` and `||` are special-cased.
## In these languages the compiler will skip evaluating the expression after the
## first operator under certain circumstances. For example an expression like
## `enable_pets && likes_dogs(user)` would compile to.
## ```roc
## if enable_pets then
## likes_dogs(user)
## else
## Bool.false
## ```
## Roc does not do this because conditionals like `if` and `when` have a
## performance cost. Calling a function can sometimes be faster across the board
## than doing an `if` to decide whether to skip calling it.
and : Bool, Bool -> Bool
## Returns `Bool.true` when either input is a `Bool.true`. This is equivalent to
## the logic [OR](https://en.wikipedia.org/wiki/Logical_disjunction) gate.
## The infix operator `||` can also be used as shorthand for `Bool.or`.
## ```roc
## expect Bool.or(Bool.false, Bool.true) == Bool.true
## expect (Bool.true || Bool.true) == Bool.true
## expect (Bool.false || Bool.true) == Bool.true
## expect (Bool.true || Bool.false) == Bool.true
## expect (Bool.false || Bool.false) == Bool.false
## ```
##
## ## Performance Details
##
## In Roc the `&&` and `||` work the same way as any
## other functions. However, in some languages `&&` and `||` are special-cased.
## Refer to the note in `Bool.and` for more detail.
or : Bool, Bool -> Bool
## Returns `Bool.false` when given `Bool.true`, and vice versa. This is
## equivalent to the logic [NOT](https://en.wikipedia.org/wiki/Negation)
## gate. The operator `!` can also be used as shorthand for `Bool.not`.

View file

@ -170,7 +170,7 @@ reserve = |@Dict({ buckets, data, max_bucket_capacity: original_max_bucket_capac
size = Num.min(requested_size, max_size)
requested_shifts = calc_shifts_for_size(size, max_load_factor)
if List.is_empty(buckets) || requested_shifts > shifts then
if List.is_empty(buckets) or requested_shifts > shifts then
(buckets0, max_bucket_capacity) = alloc_buckets_from_shift(requested_shifts, max_load_factor)
buckets1 = fill_buckets_from_data(buckets0, data, requested_shifts)
@Dict(
@ -915,7 +915,7 @@ calc_shifts_for_size_helper = |shifts, size, max_load_factor|
|> Num.to_f32
|> Num.mul(max_load_factor)
|> Num.floor
if shifts > 0 && max_bucket_capacity < size then
if shifts > 0 and max_bucket_capacity < size then
calc_shifts_for_size_helper(Num.sub_wrap(shifts, 1), size, max_load_factor)
else
shifts
@ -1087,7 +1087,7 @@ expect
|> insert("bar", {})
|> insert("baz", {})
contains(dict, "baz") && !(contains(dict, "other"))
contains(dict, "baz") and !(contains(dict, "other"))
expect
dict =
@ -1145,17 +1145,17 @@ expect
|> insert("l", 11)
(get(dict, "a") == Ok(0))
&& (get(dict, "b") == Ok(1))
&& (get(dict, "c") == Ok(2))
&& (get(dict, "d") == Ok(3))
&& (get(dict, "e") == Ok(4))
&& (get(dict, "f") == Ok(5))
&& (get(dict, "g") == Ok(6))
&& (get(dict, "h") == Ok(7))
&& (get(dict, "i") == Ok(8))
&& (get(dict, "j") == Ok(9))
&& (get(dict, "k") == Ok(10))
&& (get(dict, "l") == Ok(11))
and (get(dict, "b") == Ok(1))
and (get(dict, "c") == Ok(2))
and (get(dict, "d") == Ok(3))
and (get(dict, "e") == Ok(4))
and (get(dict, "f") == Ok(5))
and (get(dict, "g") == Ok(6))
and (get(dict, "h") == Ok(7))
and (get(dict, "i") == Ok(8))
and (get(dict, "j") == Ok(9))
and (get(dict, "k") == Ok(10))
and (get(dict, "l") == Ok(11))
# Force rehash.
expect
@ -1197,18 +1197,18 @@ expect
|> insert("m", 12)
(get(dict, "a") == Ok(0))
&& (get(dict, "b") == Ok(1))
&& (get(dict, "c") == Ok(2))
&& (get(dict, "d") == Ok(3))
&& (get(dict, "e") == Ok(4))
&& (get(dict, "f") == Ok(5))
&& (get(dict, "g") == Ok(6))
&& (get(dict, "h") == Ok(7))
&& (get(dict, "i") == Ok(8))
&& (get(dict, "j") == Ok(9))
&& (get(dict, "k") == Ok(10))
&& (get(dict, "l") == Ok(11))
&& (get(dict, "m") == Ok(12))
and (get(dict, "b") == Ok(1))
and (get(dict, "c") == Ok(2))
and (get(dict, "d") == Ok(3))
and (get(dict, "e") == Ok(4))
and (get(dict, "f") == Ok(5))
and (get(dict, "g") == Ok(6))
and (get(dict, "h") == Ok(7))
and (get(dict, "i") == Ok(8))
and (get(dict, "j") == Ok(9))
and (get(dict, "k") == Ok(10))
and (get(dict, "l") == Ok(11))
and (get(dict, "m") == Ok(12))
expect
empty({})
@ -1266,7 +1266,7 @@ expect
bad_keys,
Bool.true,
|acc, k|
acc && Dict.contains(dict, k),
acc and Dict.contains(dict, k),
)
all_inserted_correctly

View file

@ -1361,7 +1361,7 @@ split_last = |list, delimiter|
## result is an empty list.
chunks_of : List a, U64 -> List (List a)
chunks_of = |list, chunk_size|
if chunk_size == 0 || List.is_empty(list) then
if chunk_size == 0 or List.is_empty(list) then
[]
else
chunk_capacity = Num.div_ceil(List.len(list), chunk_size)

View file

@ -619,9 +619,9 @@ is_gte : Num a, Num a -> Bool
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
is_approx_eq : Frac a, Frac a, { rtol ?? Frac a, atol ?? Frac a } -> Bool
is_approx_eq = |x, y, { rtol ?? 0.00001, atol ?? 0.00000001 }|
eq = x <= y && x >= y
eq = x <= y and x >= y
meets_tolerance = Num.abs_diff(x, y) <= Num.max(atol, (rtol * Num.max(Num.abs(x), Num.abs(y))))
eq || meets_tolerance
eq or meets_tolerance
## Returns `Bool.true` if the number is `0`, and `Bool.false` otherwise.
is_zero : Num a -> Bool

View file

@ -972,7 +972,7 @@ matches_at_help = |state|
},
)
does_this_match && does_rest_match
does_this_match and does_rest_match
## Walks over the `UTF-8` bytes of the given [Str] and calls a function to update
## state for each byte. The index for that byte in the string is provided

View file

@ -212,8 +212,6 @@ map_symbol_to_lowlevel_and_arity! {
Eq; BOOL_STRUCTURAL_EQ; 2,
NotEq; BOOL_STRUCTURAL_NOT_EQ; 2,
And; BOOL_AND; 2,
Or; BOOL_OR; 2,
Not; BOOL_NOT; 1,
BoxExpr; BOX_BOX_FUNCTION; 1,
UnboxExpr; BOX_UNBOX; 1,

View file

@ -4,7 +4,6 @@ use crate::env::Env;
use crate::scope::Scope;
use bumpalo::collections::Vec;
use roc_error_macros::internal_error;
use roc_module::called_via::BinOp::{DoubleQuestion, Pizza, SingleQuestion};
use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::ModuleName;
use roc_parse::ast::Expr::{self, *};
@ -36,7 +35,7 @@ fn new_op_call_expr<'a>(
let value = match loc_op.value {
// Rewrite the Pizza operator into an Apply
Pizza => {
BinOp::Pizza => {
// Allow `left |> try (optional)` to desugar to `try left (optional)`
let right_without_spaces = without_spaces(&right.value);
match right_without_spaces {
@ -190,7 +189,7 @@ fn new_op_call_expr<'a>(
let args = args.into_bump_slice();
Apply(function, args, CalledVia::BinOp(Pizza))
Apply(function, args, CalledVia::BinOp(BinOp::Pizza))
}
PncApply(function, arguments) => {
let mut args = Vec::with_capacity_in(1 + arguments.len(), env.arena);
@ -200,16 +199,20 @@ fn new_op_call_expr<'a>(
let args = args.into_bump_slice();
Apply(function, args, CalledVia::BinOp(Pizza))
Apply(function, args, CalledVia::BinOp(BinOp::Pizza))
}
Dbg => *desugar_dbg_expr(env, scope, left, region),
_ => {
// e.g. `1 |> (if b then (\a -> a) else (\c -> c))`
Apply(right, env.arena.alloc([left]), CalledVia::BinOp(Pizza))
Apply(
right,
env.arena.alloc([left]),
CalledVia::BinOp(BinOp::Pizza),
)
}
}
}
DoubleQuestion => {
BinOp::DoubleQuestion => {
let left = desugar_expr(env, scope, left);
let right = desugar_expr(env, scope, right);
@ -259,7 +262,7 @@ fn new_op_call_expr<'a>(
When(left, &*env.arena.alloc([ok_branch, err_branch]))
}
SingleQuestion => {
BinOp::SingleQuestion => {
let left = desugar_expr(env, scope, left);
let right = desugar_expr(env, scope, right);
@ -340,6 +343,41 @@ fn new_op_call_expr<'a>(
When(left, &*env.arena.alloc([ok_branch, err_branch]))
}
BinOp::And => {
let left = desugar_expr(env, scope, left);
let right = desugar_expr(env, scope, right);
Expr::If {
if_thens: env.arena.alloc([(*left, *right)]),
final_else: env.arena.alloc(Loc::at(
right.region,
Expr::Var {
module_name: ModuleName::BOOL,
ident: "false",
},
)),
indented_else: false,
}
}
BinOp::Or => {
let left = desugar_expr(env, scope, left);
let right = desugar_expr(env, scope, right);
Expr::If {
if_thens: env.arena.alloc([(
*left,
Loc::at(
right.region,
Expr::Var {
module_name: ModuleName::BOOL,
ident: "true",
},
),
)]),
final_else: right,
indented_else: false,
}
}
binop => {
let left = desugar_expr(env, scope, left);
let right = desugar_expr(env, scope, right);
@ -1641,8 +1679,8 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) {
GreaterThan => (ModuleName::NUM, "is_gt"),
LessThanOrEq => (ModuleName::NUM, "is_lte"),
GreaterThanOrEq => (ModuleName::NUM, "is_gte"),
And => (ModuleName::BOOL, "and"),
Or => (ModuleName::BOOL, "or"),
And => unreachable!("Cannot desugar the `and` operator"),
Or => unreachable!("Cannot desugar the `or` operator"),
Pizza => unreachable!("Cannot desugar the |> operator"),
DoubleQuestion => unreachable!("Cannot desugar the ?? operator"),
SingleQuestion => unreachable!("Cannot desugar the ? operator"),

View file

@ -959,8 +959,8 @@ fn push_op(buf: &mut Buf, op: BinOp) {
called_via::BinOp::GreaterThan => buf.push('>'),
called_via::BinOp::LessThanOrEq => buf.push_str("<="),
called_via::BinOp::GreaterThanOrEq => buf.push_str(">="),
called_via::BinOp::And => buf.push_str("&&"),
called_via::BinOp::Or => buf.push_str("||"),
called_via::BinOp::Or => buf.push_str("or"),
called_via::BinOp::And => buf.push_str("and"),
called_via::BinOp::Pizza => buf.push_str("|>"),
called_via::BinOp::DoubleQuestion => buf.push_str("??"),
called_via::BinOp::SingleQuestion => buf.push_str("?"),

View file

@ -1268,20 +1268,6 @@ trait Backend<'a> {
internal_error!("bitwise xor on a non-integer")
}
}
LowLevel::And => {
if let LayoutRepr::Builtin(Builtin::Bool) = self.interner().get_repr(*ret_layout) {
self.build_int_bitwise_and(sym, &args[0], &args[1], IntWidth::U8)
} else {
internal_error!("bitwise and on a non-integer")
}
}
LowLevel::Or => {
if let LayoutRepr::Builtin(Builtin::Bool) = self.interner().get_repr(*ret_layout) {
self.build_int_bitwise_or(sym, &args[0], &args[1], IntWidth::U8)
} else {
internal_error!("bitwise or on a non-integer")
}
}
LowLevel::NumShiftLeftBy => {
if let LayoutRepr::Builtin(Builtin::Int(int_width)) =
self.interner().get_repr(*ret_layout)

View file

@ -1284,30 +1284,6 @@ pub(crate) fn run_low_level<'a, 'ctx>(
rhs_layout,
)
}
And => {
// The (&&) operator
arguments!(lhs_arg, rhs_arg);
let bool_val = env.builder.new_build_and(
lhs_arg.into_int_value(),
rhs_arg.into_int_value(),
"bool_and",
);
BasicValueEnum::IntValue(bool_val)
}
Or => {
// The (||) operator
arguments!(lhs_arg, rhs_arg);
let bool_val = env.builder.new_build_or(
lhs_arg.into_int_value(),
rhs_arg.into_int_value(),
"bool_or",
);
BasicValueEnum::IntValue(bool_val)
}
Not => {
// The (!) operator
arguments!(arg);

View file

@ -2163,14 +2163,6 @@ impl<'a> LowLevelCall<'a> {
NumF64ToParts => self.load_args_and_call_zig(backend, bitcode::NUM_F64_TO_PARTS),
NumF32FromParts => self.load_args_and_call_zig(backend, bitcode::NUM_F32_FROM_PARTS),
NumF64FromParts => self.load_args_and_call_zig(backend, bitcode::NUM_F64_FROM_PARTS),
And => {
self.load_args(backend);
backend.code_builder.i32_and();
}
Or => {
self.load_args(backend);
backend.code_builder.i32_or();
}
Not => {
self.load_args(backend);
backend.code_builder.i32_eqz();

View file

@ -6036,7 +6036,7 @@ All branches in an `if` must have the same type!
double_binop,
indoc!(
r"
key >= 97 && <= 122
key >= 97 and <= 122
"
),
@r"

View file

@ -62,8 +62,8 @@ const DISPLAY_STRINGS: [(BinOp, &str); 18] = [
(GreaterThan, ">"),
(LessThanOrEq, "<="),
(GreaterThanOrEq, ">="),
(And, "&&"),
(Or, "||"),
(And, "and"),
(Or, "or"),
];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -199,7 +199,7 @@ pub enum Associativity {
/// right-associative operators:
///
/// exponentiation: ^
/// boolean: && ||
/// boolean: and or
/// application: <|
RightAssociative,

View file

@ -109,8 +109,6 @@ pub enum LowLevel {
NumF64FromParts,
Eq,
NotEq,
And,
Or,
Not,
Hash,
PtrCast,
@ -343,8 +341,6 @@ map_symbol_to_lowlevel! {
NumF64FromParts <= NUM_F64_FROM_PARTS;
Eq <= BOOL_STRUCTURAL_EQ;
NotEq <= BOOL_STRUCTURAL_NOT_EQ;
And <= BOOL_AND;
Or <= BOOL_OR;
Not <= BOOL_NOT;
Unreachable <= LIST_UNREACHABLE;
DictPseudoSeed <= DICT_PSEUDO_SEED;

View file

@ -1355,16 +1355,14 @@ define_builtins! {
0 BOOL_BOOL: "Bool" exposed_type=true // the Bool.Bool type alias
1 BOOL_FALSE: "false"
2 BOOL_TRUE: "true"
3 BOOL_AND: "and"
4 BOOL_OR: "or"
5 BOOL_NOT: "not"
6 BOOL_XOR: "xor"
7 BOOL_NEQ: "is_not_eq"
8 BOOL_EQ: "Eq" exposed_type=true
9 BOOL_IS_EQ: "is_eq"
10 BOOL_IS_EQ_IMPL: "bool_is_eq"
unexposed 11 BOOL_STRUCTURAL_EQ: "structural_eq"
unexposed 12 BOOL_STRUCTURAL_NOT_EQ: "structural_not_eq"
3 BOOL_NOT: "not"
4 BOOL_XOR: "xor"
5 BOOL_NEQ: "is_not_eq"
6 BOOL_EQ: "Eq" exposed_type=true
7 BOOL_IS_EQ: "is_eq"
8 BOOL_IS_EQ_IMPL: "bool_is_eq"
unexposed 9 BOOL_STRUCTURAL_EQ: "structural_eq"
unexposed 10 BOOL_STRUCTURAL_NOT_EQ: "structural_not_eq"
}
5 STR: "Str" => {
0 STR_STR: "Str" exposed_apply_type=true // the Str.Str type alias

View file

@ -1562,7 +1562,7 @@ fn low_level_no_rc(lowlevel: &LowLevel) -> RC {
Eq | NotEq => RC::NoRc,
And | Or | NumAdd | NumAddWrap | NumAddChecked | NumAddSaturated | NumSub | NumSubWrap
NumAdd | NumAddWrap | NumAddChecked | NumAddSaturated | NumSub | NumSubWrap
| NumSubChecked | NumSubSaturated | NumMul | NumMulWrap | NumMulSaturated
| NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare | NumDivFrac
| NumDivTruncUnchecked | NumDivCeilUnchecked | NumRemUnchecked | NumIsMultipleOf

View file

@ -1261,7 +1261,7 @@ pub(crate) fn lowlevel_borrow_signature(op: LowLevel) -> &'static [Ownership] {
Eq | NotEq => &[BORROWED, BORROWED],
And | Or | NumAdd | NumAddWrap | NumAddChecked | NumAddSaturated | NumSub | NumSubWrap
NumAdd | NumAddWrap | NumAddChecked | NumAddSaturated | NumSub | NumSubWrap
| NumSubChecked | NumSubSaturated | NumMul | NumMulWrap | NumMulSaturated
| NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare | NumDivFrac
| NumDivTruncUnchecked | NumDivCeilUnchecked | NumRemUnchecked | NumIsMultipleOf

View file

@ -738,7 +738,7 @@ fn parse_stmt_operator_chain<'a>(
| Expr::Apply(
Loc {
region: _,
value: Expr::Tag(..)
value: Expr::Tag(_)
},
&[],
_
@ -752,7 +752,7 @@ fn parse_stmt_operator_chain<'a>(
// try an operator
return parse_stmt_after_apply(
arena,
state.clone(),
state,
min_indent,
call_min_indent,
expr_state,
@ -1934,43 +1934,6 @@ fn parse_stmt_after_apply<'a>(
}
}
// #[allow(clippy::too_many_arguments)]
// fn parse_expr_after_apply<'a>(
// arena: &'a Bump,
// state: State<'a>,
// min_indent: u32,
// call_min_indent: u32,
// check_for_arrow: CheckForArrow,
// check_for_defs: bool,
// mut expr_state: ExprState<'a>,
// before_op: State<'a>,
// initial_state: State<'a>,
// ) -> Result<(Progress, Expr<'a>, State<'a>), (Progress, EExpr<'a>)> {
// match loc(bin_op(check_for_defs)).parse(arena, state.clone(), call_min_indent) {
// Err((MadeProgress, f)) => Err((MadeProgress, f)),
// Ok((_, loc_op, state)) => {
// expr_state.consume_spaces(arena);
// let initial_state = before_op;
// parse_expr_operator(
// arena,
// state,
// min_indent,
// call_min_indent,
// options,
// check_for_defs,
// expr_state,
// loc_op,
// initial_state,
// )
// }
// Err((NoProgress, _)) => {
// let expr = parse_expr_final(expr_state, arena);
// // roll back space parsing
// Ok((MadeProgress, expr, initial_state))
// }
// }
// }
#[allow(clippy::too_many_arguments)]
fn parse_apply_arg<'a>(
arena: &'a Bump,
@ -4069,6 +4032,24 @@ where
G: Fn(&'a str, Position) -> E,
E: 'a,
{
match state.bytes() {
&[b'o', b'r', ..] => {
return Ok((
MadeProgress,
OperatorOrDef::BinOp(BinOp::Or),
state.advance(2),
))
}
&[b'a', b'n', b'd', ..] => {
return Ok((
MadeProgress,
OperatorOrDef::BinOp(BinOp::And),
state.advance(3),
))
}
_ => {}
}
let chomped = chomp_ops(state.bytes());
macro_rules! good {

View file

@ -10,6 +10,8 @@ pub const IMPORT: &str = "import";
pub const EXPECT: &str = "expect";
pub const RETURN: &str = "return";
pub const CRASH: &str = "crash";
pub const AND: &str = "and";
pub const OR: &str = "or";
// These keywords are valid in imports
pub const EXPOSING: &str = "exposing";
@ -21,8 +23,8 @@ pub const WHERE: &str = "where";
// These keywords are valid in headers
pub const PLATFORM: &str = "platform";
pub const KEYWORDS: [&str; 11] = [
IF, THEN, ELSE, WHEN, AS, IS, DBG, IMPORT, EXPECT, RETURN, CRASH,
pub const KEYWORDS: [&str; 13] = [
IF, THEN, ELSE, WHEN, AS, IS, DBG, IMPORT, EXPECT, RETURN, CRASH, AND, OR,
];
pub fn is_allowed_identifier(mut ident: &str) -> bool {

View file

@ -2095,13 +2095,13 @@ mod eq {
LyingEq := U8 implements [Eq {is_eq}]
is_eq = \@LyingEq m, @LyingEq n -> m != n
is_eq = \@LyingEq(m), @LyingEq(n) -> m != n
main =
a = @LyingEq 10
b = @LyingEq 5
c = @LyingEq 5
if Bool.is_eq a b && !(Bool.is_eq b c) then
a = @LyingEq(10)
b = @LyingEq(5)
c = @LyingEq(5)
if Bool.is_eq(a, b) and !(Bool.is_eq(b, c)) then
"okay"
else
"fail"

View file

@ -121,7 +121,7 @@ fn bool_logic() {
bool2 = Bool.false
bool3 = !bool1
(bool1 && bool2) || bool2 && bool3
(bool1 and bool2) or bool2 and bool3
"#
),
false,
@ -132,19 +132,19 @@ fn bool_logic() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn and_bool() {
assert_evals_to!("Bool.true && Bool.true", true, bool);
assert_evals_to!("Bool.true && Bool.false", false, bool);
assert_evals_to!("Bool.false && Bool.true", false, bool);
assert_evals_to!("Bool.false && Bool.false", false, bool);
assert_evals_to!("Bool.true and Bool.true", true, bool);
assert_evals_to!("Bool.true and Bool.false", false, bool);
assert_evals_to!("Bool.false and Bool.true", false, bool);
assert_evals_to!("Bool.false and Bool.false", false, bool);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn or_bool() {
assert_evals_to!("Bool.true || Bool.true", true, bool);
assert_evals_to!("Bool.true || Bool.false", true, bool);
assert_evals_to!("Bool.false || Bool.true", true, bool);
assert_evals_to!("Bool.false || Bool.false", false, bool);
assert_evals_to!("Bool.true or Bool.true", true, bool);
assert_evals_to!("Bool.true or Bool.false", true, bool);
assert_evals_to!("Bool.false or Bool.true", true, bool);
assert_evals_to!("Bool.false or Bool.false", false, bool);
}
#[test]
@ -544,7 +544,7 @@ fn eq_different_rosetrees() {
cd = c2 == d2
ab && cd
ab and cd
"#
),
true,

View file

@ -158,19 +158,19 @@ fn even_odd() {
assert_evals_to!(
indoc!(
r"
even = \n ->
even = |n|
when n is
0 -> Bool.true
1 -> Bool.false
_ -> odd (n - 1)
_ -> odd(n - 1)
odd = \n ->
odd = |n|
when n is
0 -> Bool.false
1 -> Bool.true
_ -> even (n - 1)
_ -> even(n - 1)
odd 5 && even 42
odd(5) and even(42)
"
),
true,
@ -2315,12 +2315,12 @@ fn recursive_tag_id_in_allocation_eq() {
]
x : Value
x = G 42
x = G(42)
y : Value
y = H 42
y = H(42)
main = (x == x) && (x != y) && (y == y)
main = x == x and x != y and y == y
"#
),
true,

View file

@ -2460,7 +2460,7 @@ fn issue_4557() {
app "test" provides [main] to "./platform"
is_eq_q = \q1, q2 -> when T q1 q2 is
T (U f1) (U f2) -> Bool.or (is_eq_q (U f2) (U f1)) (f1 {} == f2 {})
T (U f1) (U f2) -> (is_eq_q (U f2) (U f1)) or (f1 {} == f2 {})
main = is_eq_q (U \{} -> "a") (U \{} -> "a")
"#

View file

@ -2,11 +2,11 @@ app "test" provides [main] to "./platform"
main =
s1 : Set U8
s1 = Set.empty {}
s1 = Set.empty({})
s2 : Set Str
s2 = Set.empty {}
s2 = Set.empty({})
Bool.is_eq s1 s1 && Bool.is_eq s2 s2
# ^^^^^^^^^^ Set#Bool.is_eq(31): Set Str, Set Str -[[Set.is_eq(31)]]-> Bool
Bool.is_eq(s1, s1) and Bool.is_eq(s2, s2)
# ^^^^^^^^^^ Set#Bool.is_eq(31): Set Str, Set Str -[[Set.is_eq(31)]]-> Bool
# ^^^^^^^^^^ Set#Bool.is_eq(31): Set U8, Set U8 -[[Set.is_eq(31)]]-> Bool

View file

@ -1797,7 +1797,7 @@ generate_derive_str = \buf, types, type, include_debug ->
can_support_eq_hash_ord : Types, Shape -> Bool
can_support_eq_hash_ord = \types, type ->
!(has_float(types, type)) && (can_support_partial_eq_ord(types, type))
!(has_float(types, type)) and (can_support_partial_eq_ord(types, type))
can_support_partial_eq_ord : Types, Shape -> Bool
can_support_partial_eq_ord = \types, type ->
@ -1817,7 +1817,7 @@ can_support_partial_eq_ord = \types, type ->
k_type = Types.shape(types, k)
v_type = Types.shape(types, v)
can_support_partial_eq_ord(types, k_type) && can_support_partial_eq_ord(types, v_type)
can_support_partial_eq_ord(types, k_type) and can_support_partial_eq_ord(types, v_type)
TagUnion(Recursive({ tags })) ->
List.all(tags, \{ payload } ->
@ -1854,7 +1854,7 @@ can_support_partial_eq_ord = \types, type ->
ok_shape = Types.shape(types, ok_id)
err_shape = Types.shape(types, err_id)
can_support_partial_eq_ord(types, ok_shape) && can_support_partial_eq_ord(types, err_shape)
can_support_partial_eq_ord(types, ok_shape) and can_support_partial_eq_ord(types, err_shape)
Struct({ fields: HasNoClosure(fields) }) | TagUnionPayload({ fields: HasNoClosure(fields) }) ->
List.all(fields, \{ id } -> can_support_partial_eq_ord(types, Types.shape(types, id)))
@ -1891,7 +1891,7 @@ can_derive_copy = \types, type ->
RocResult(ok_id, err_id) ->
can_derive_copy(types, Types.shape(types, ok_id))
&& can_derive_copy(types, Types.shape(types, err_id))
and can_derive_copy(types, Types.shape(types, err_id))
Struct({ fields: HasNoClosure(fields) }) | TagUnionPayload({ fields: HasNoClosure(fields) }) ->
List.all(fields, \{ id } -> can_derive_copy(types, Types.shape(types, id)))
@ -1909,7 +1909,7 @@ cannot_support_default = \types, type ->
TagUnionPayload({ fields: HasClosure(_) }) -> Bool.true
RocDict(key_id, val_id) ->
cannot_support_copy(types, Types.shape(types, key_id))
|| cannot_support_copy(types, Types.shape(types, val_id))
or cannot_support_copy(types, Types.shape(types, val_id))
Struct({ fields: HasClosure(_) }) -> Bool.true
Struct({ fields: HasNoClosure(fields) }) | TagUnionPayload({ fields: HasNoClosure(fields) }) ->
@ -1934,7 +1934,7 @@ has_float_help = \types, type, do_not_recurse ->
RocDict(id0, id1) | RocResult(id0, id1) ->
has_float_help(types, Types.shape(types, id0), do_not_recurse)
|| has_float_help(types, Types.shape(types, id1), do_not_recurse)
or has_float_help(types, Types.shape(types, id1), do_not_recurse)
Struct({ fields: HasNoClosure(fields) }) | TagUnionPayload({ fields: HasNoClosure(fields) }) ->
List.any(fields, \{ id } -> has_float_help(types, Types.shape(types, id), do_not_recurse))
@ -2160,7 +2160,7 @@ contains_refcounted_help = \types, type, do_not_recurse ->
RocResult(id0, id1) ->
contains_refcounted_help(types, Types.shape(types, id0), do_not_recurse)
|| contains_refcounted_help(types, Types.shape(types, id1), do_not_recurse)
or contains_refcounted_help(types, Types.shape(types, id1), do_not_recurse)
Struct({ fields: HasNoClosure(fields) }) | TagUnionPayload({ fields: HasNoClosure(fields) }) ->
List.any(fields, \{ id } -> contains_refcounted_help(types, Types.shape(types, id), do_not_recurse))

View file

@ -247,8 +247,6 @@ fn to_expr_report<'a>(
let suggestion = match *op {
"|" => vec![
alloc.reflow("Maybe you want "),
alloc.parser_suggestion("||"),
alloc.reflow(" or "),
alloc.parser_suggestion("|>"),
alloc.reflow(" instead?"),
],

View file

@ -73,7 +73,7 @@ tokens_to_str : List Token -> Str
tokens_to_str = \tokens ->
List.walk(tokens, "", \buf, token ->
buf_with_space =
if Str.is_empty(buf) || token == Literal(",") then
if Str.is_empty(buf) or token == Literal(",") then
buf
else
Str.concat(buf, " ")

View file

@ -242,14 +242,14 @@ fromU8 = \r, g, b, a -> @Color (RgbaU8 r g b a)
fromI16 : I16, I16, I16, I16 -> Result Color [OutOfRange]
fromI16 = \r, g, b, a ->
if r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 || a < 0 || a > 255 then
if r < 0 or r > 255 or g < 0 or g > 255 or b < 0 or b > 255 or a < 0 or a > 255 then
Err OutOfRange
else
Ok (@Color (RgbaU8 (Num.toU8 r) (Num.toU8 g) (Num.toU8 b) (Num.toU8 a)))
fromF32 : F32, F32, F32, F32 -> Result Color [OutOfRange]
fromF32 = \r, g, b, a ->
if r < 0.0 || r > 1.0 || g < 0.0 || g > 1.0 || b < 0.0 || b > 1.0 || a < 0.0 || a > 1.0 then
if r < 0.0 or r > 1.0 or g < 0.0 or g > 1.0 or b < 0.0 or b > 1.0 or a < 0.0 or a > 1.0 then
Err OutOfRange
else
Ok (@Color (RgbaF32 r g b a))

View file

@ -359,8 +359,8 @@ As a historical note, these stylistic benefits (of `|> Num.sub 1` working as exp
### [Currying and learning curve](#curried-learning-curve) {#curried-learning-curve}
Currying leads to function signatures that look surprising to beginners. For example, in Roc, the
[`Bool.and`](https://www.roc-lang.org/builtins/Bool#and) function has the type `Bool, Bool -> Bool`. If Roc were a
curried language, this function would instead have the type `Bool -> Bool -> Bool`. Since no mainstream programming
[`Str.concat`](https://www.roc-lang.org/builtins/Str#concat) function has the type `Str, Str -> Str`. If Roc were a
curried language, this function would instead have the type `Str -> Str -> Str`. Since no mainstream programming
languages today are curried, anyone who knows a mainstream language and is learning their first curried language will
require additional explanation about why function types look this way.

View file

@ -640,7 +640,7 @@ We refer to whatever comes before a `->` in a `when` expression as a _pattern_
In many programming languages, `true` and `false` are special language keywords that refer to the two [boolean](https://en.wikipedia.org/wiki/Boolean_data_type) values. In Roc, booleans do not get special keywords; instead, they are exposed as the ordinary values `Bool.true` and `Bool.false`.
This design is partly to keep the number of special keywords in the language smaller, but mainly to suggest how booleans are intended to be used in Roc: for [_boolean logic_](https://en.wikipedia.org/wiki/Boolean_algebra) (`&&`, `||`, and so on) as opposed to for data modeling. Tags are the preferred choice for data modeling, and having tag values be more concise than boolean values helps make this preference clear.
This design is partly to keep the number of special keywords in the language smaller, but mainly to suggest how booleans are intended to be used in Roc: for [_boolean logic_](https://en.wikipedia.org/wiki/Boolean_algebra) (`and`, `or`, and so on) as opposed to for data modeling. Tags are the preferred choice for data modeling, and having tag values be more concise than boolean values helps make this preference clear.
As an example of why tags are encouraged for data modeling, in many languages it would be common to write a record like `{ name: "Richard", isAdmin: Bool.true }`, but in Roc it would be preferable to write something like `{ name: "Richard", role: Admin }`. At first, the `role` field might only ever be set to `Admin` or `Normal`, but because the data has been modeled using tags instead of booleans, it's much easier to add other alternatives in the future, like `Guest` or `Moderator` - some of which might also want payloads.
@ -2369,22 +2369,21 @@ Here are various Roc expressions involving operators, and what they desugar to.
| Expression | Desugars To |
| ---------------------------- | ---------------------------------------------------------------------------- |
| `a + b` | `Num.add a b` |
| `a - b` | `Num.sub a b` |
| `a * b` | `Num.mul a b` |
| `a / b` | `Num.div a b` |
| `a // b` | `Num.divTrunc a b` |
| `a ^ b` | `Num.pow a b` |
| `a % b` | `Num.rem a b` |
| `-a` | `Num.neg a` |
| `a == b` | `Bool.isEq a b` |
| `a != b` | `Bool.isNotEq a b` |
| `a && b` | `Bool.and a b` |
| <code>a \|\| b</code> | `Bool.or a b` |
| `!a` | `Bool.not a` |
| <code>a \|> f</code> | `f a` |
| <code>f a b \|> g x y</code> | `g (f a b) x y` |
| `f!` | [see example](https://www.roc-lang.org/examples/DesugaringAwait/README.html) |
| `a + b` | `Num.add(a, b)` |
| `a - b` | `Num.sub(a, b)` |
| `a * b` | `Num.mul(a, b)` |
| `a / b` | `Num.div(a, b)` |
| `a // b` | `Num.div_trunc(a, b)` |
| `a ^ b` | `Num.pow(a, b)` |
| `a % b` | `Num.rem(a, b)` |
| `-a` | `Num.neg(a)` |
| `a == b` | `Bool.is_eq(a, b)` |
| `a != b` | `Bool.is_not_eq(a, b)` |
| `a and b` | `if a then b else Bool.false` |
| `a or b` | `if a then Bool.true else b` |
| `!a` | `Bool.not(a)` |
| <code>a \|> f</code> | `f(a)` |
| <code>f a b \|> g x y</code> | `g(f(a, b), x, y)` |
| `f?` | [see example](https://www.roc-lang.org/examples/DesugaringTry/README.html) |
### [Additional Resources](#additional-resources) {#additional-resources}

View file

@ -111,7 +111,7 @@ view = \page_path_str, html_content ->
"/index.html" -> [id("homepage-main")]
"/tutorial.html" -> [id("tutorial-main"), class("article-layout")]
_ ->
if Str.starts_with(page_path_str, "/examples/") && page_path_str != "/examples/index.html" then
if Str.starts_with(page_path_str, "/examples/") and page_path_str != "/examples/index.html" then
# Individual examples should render wider than articles.
# Otherwise the width is unreasonably low for the code blocks,
# and those pages don't tend to have big paragraphs anyway.