Add Gleam language integration to Elixir project

- Rename Gleam module from encoding_simple to encoding
- Move Gleam files from src/aprs/ to src/aprsme/ to match project namespace
- Create custom Mix task for Gleam compilation (lib/mix/tasks/gleam_compile.ex)
- Update EncodingUtils to wrap Gleam implementation instead of pure Elixir
- Add Gleam dependencies to mix.exs and configure build paths
- Update Mix aliases to include Gleam compilation in test and compile tasks
- Add Gleam support to GitHub Actions CI workflow with caching
- Add GLEAM_INTEGRATION.md documentation
- All 357 tests passing with Gleam integration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Graham McIntire 2025-07-18 13:58:21 -05:00
parent 92585ae34f
commit 00a8f996f4
No known key found for this signature in database
267 changed files with 42488 additions and 115 deletions

View file

@ -59,6 +59,31 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
# Step: Install Gleam
- name: Set up Gleam
uses: gleam-lang/setup-gleam@v1
with:
gleam-version: "1.5.1"
# Step: Install mix_gleam archive
- name: Install mix_gleam
run: mix archive.install hex mix_gleam 0.6.2 --force
# Step: Cache Gleam packages
- name: Cache Gleam packages
id: cache-gleam
uses: actions/cache@v4
env:
cache-name: cache-gleam-packages
with:
path: |
build
~/.cache/gleam
key: ${{ runner.os }}-gleam-${{ env.cache-name }}-${{ hashFiles('**/gleam.toml') }}
restore-keys: |
${{ runner.os }}-gleam-${{ env.cache-name }}-
${{ runner.os }}-gleam-
# Step: Define how to cache deps. Restores existing cache if present.
- name: Cache deps
id: cache-deps
@ -104,6 +129,10 @@ jobs:
- name: Install dependencies
run: mix deps.get
# Step: Compile Gleam code first
- name: Compile Gleam code
run: mix gleam_compile
# Step: Compile the project treating any warnings as errors.
# Customize this step if a different behavior is desired.
- name: Compiles without warnings

85
GLEAM_INTEGRATION.md Normal file
View file

@ -0,0 +1,85 @@
# Gleam Integration Guide
This document describes how Gleam has been integrated into the APRS.me Elixir project.
## Setup
1. **Mix Gleam Archive**: Installed via `mix archive.install hex mix_gleam`
2. **Dependencies**: Added to mix.exs:
```elixir
{:gleam_stdlib, ">= 0.60.0 and < 1.0.0", app: false, override: true},
{:gleeunit, "~> 1.0", only: [:dev, :test], runtime: false, app: false}
```
3. **Project Configuration**: Added to mix.exs project config:
```elixir
archives: [mix_gleam: "~> 0.6"],
erlc_paths: ["build/dev/erlang/aprsme/_gleam_artefacts", "src"],
erlc_include_path: "build/dev/erlang/aprsme/include",
```
## File Structure
- `/src/` - Gleam source files
- `/src/aprs/` - APRS-specific Gleam modules
- `/gleam.toml` - Gleam project configuration
## Compilation
The project is configured to automatically compile Gleam code when running tests or compiling:
```bash
# For development
mix compile.gleam && mix compile
# For tests (automatically compiles Gleam)
mix test
# Manual compilation if needed
mix gleam_compile
```
The custom `gleam_compile` task handles:
- Running the mix_gleam compiler when available
- Falling back to the `gleam` binary if mix_gleam isn't installed
- Copying compiled beam files to the appropriate build directory
## Module Naming
Gleam modules are compiled with `@` as the separator in BEAM files:
- Gleam: `aprs/encoding`
- BEAM: `aprs@encoding`
- Elixir: `:aprs@encoding`
## Current Modules
### encoding.gleam
A type-safe implementation of encoding utilities:
- `sanitize_string/1` - Ensures strings are valid UTF-8, handles Latin-1 conversion
- `to_float_safe/1` - Safe string to float conversion with Option type
- `to_hex/1` - Convert binary to hex string representation
- `has_weather_data/4` - Check if packet contains weather data
- `encoding_info/1` - Get encoding information about a binary
## Elixir Integration
The `Aprsme.EncodingUtils` module now wraps the Gleam implementation, replacing the original pure Elixir version. The Gleam implementation provides:
- Type-safe string sanitization with Latin-1 to UTF-8 conversion
- Proper handling of control characters
- Safe float conversion with bounds checking
- Consistent encoding validation
The migration was completed with all tests passing and no breaking changes to the API.
## Testing
The original test suite at `/test/aprsme/encoding_utils_test.exs` continues to work with the Gleam implementation:
```bash
mix test test/aprsme/encoding_utils_test.exs
```
## Future Considerations
1. Add Gleam compiler to Mix.compilers() once the integration is more stable
2. Consider migrating more type-critical modules to Gleam
3. Explore using Gleam's type system for packet validation

View file

@ -0,0 +1,100 @@
-module('aprsme@@main').
-export([run/1]).
-define(red, "\e[31;1m").
-define(grey, "\e[90m").
-define(reset_color, "\e[39m").
-define(reset_all, "\e[0m").
run(Module) ->
io:setopts(standard_io, [binary, {encoding, utf8}]),
io:setopts(standard_error, [{encoding, utf8}]),
process_flag(trap_exit, true),
Pid = spawn_link(fun() -> run_module(Module) end),
receive
{'EXIT', Pid, {Reason, StackTrace}} ->
print_error(exit, Reason, StackTrace),
init:stop(1)
end.
run_module(Module) ->
try
{ok, _} = application:ensure_all_started('aprsme'),
erlang:process_flag(trap_exit, false),
Module:main(),
erlang:halt(0)
catch
Class:Reason:StackTrace ->
print_error(Class, Reason, StackTrace),
init:stop(1)
end.
print_error(Class, Error, Stacktrace) ->
Printed = [
?red, "runtime error", ?reset_color, ": ", error_class(Class, Error), ?reset_all,
"\n\n",
error_message(Error),
"\n\n",
error_details(Class, Error),
"stacktrace:\n",
[error_frame(Line) || Line <- refine_first(Error, Stacktrace)]
],
io:format(standard_error, "~ts~n", [Printed]).
refine_first(#{gleam_error := _, line := L}, [{M, F, A, [{file, Fi} | _]} | S]) ->
[{M, F, A, [{file, Fi}, {line, L}]} | S];
refine_first(_, S) ->
S.
error_class(_, #{gleam_error := panic}) -> "panic";
error_class(_, #{gleam_error := todo}) -> "todo";
error_class(_, #{gleam_error := let_assert}) -> "let assert";
error_class(_, #{gleam_error := assert}) -> "assert";
error_class(Class, _) -> ["Erlang ", atom_to_binary(Class)].
error_message(#{gleam_error := _, message := M}) ->
M;
error_message(undef) ->
<<"A function was called but it did not exist."/utf8 >>;
error_message({case_clause, _}) ->
<<"No pattern matched in an Erlang case expression."/utf8>>;
error_message({badmatch, _}) ->
<<"An Erlang assignment pattern did not match."/utf8>>;
error_message(function_clause) ->
<<"No Erlang function clause matched the arguments it was called with."/utf8>>;
error_message(_) ->
<<"An error occurred outside of Gleam."/utf8>>.
error_details(_, #{gleam_error := let_assert, value := V}) ->
["unmatched value:\n ", print_term(V), $\n, $\n];
error_details(_, {case_clause, V}) ->
["unmatched value:\n ", print_term(V), $\n, $\n];
error_details(_, {badmatch, V}) ->
["unmatched value:\n ", print_term(V), $\n, $\n];
error_details(_, #{gleam_error := _}) ->
[];
error_details(error, function_clause) ->
[];
error_details(error, undef) ->
[];
error_details(C, E) ->
["erlang:", atom_to_binary(C), $(, print_term(E), $), $\n, $\n].
print_term(T) ->
try
gleam@string:inspect(T)
catch
_:_ -> io_lib:format("~p", [T])
end.
error_frame({?MODULE, _, _, _}) -> [];
error_frame({erl_eval, _, _, _}) -> [];
error_frame({init, _, _, _}) -> [];
error_frame({M, F, _, O}) ->
M1 = string:replace(atom_to_binary(M), "@", "/", all),
[" ", M1, $., atom_to_binary(F), error_frame_end(O), $\n].
error_frame_end([{file, Fi}, {line, L} | _]) ->
[?grey, $\s, Fi, $:, integer_to_binary(L), ?reset_all];
error_frame_end(_) ->
[?grey, " unknown source", ?reset_all].

View file

@ -0,0 +1,275 @@
-module(aprsme@encoding).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/aprsme/encoding.gleam").
-export([sanitize_string/1, to_float_safe/1, to_hex/1, has_weather_data/4, encoding_info/1]).
-export_type([encoding_info/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type encoding_info() :: {encoding_info,
boolean(),
integer(),
gleam@option:option(integer()),
gleam@option:option(integer())}.
-file("src/aprsme/encoding.gleam", 29).
-spec do_bit_array_to_list(bitstring(), list(integer())) -> list(integer()).
do_bit_array_to_list(Input, Acc) ->
case erlang:byte_size(Input) of
0 ->
Acc;
_ ->
case gleam_stdlib:bit_array_slice(Input, 0, 1) of
{ok, <<Byte>>} ->
case gleam_stdlib:bit_array_slice(
Input,
1,
erlang:byte_size(Input) - 1
) of
{ok, Rest} ->
do_bit_array_to_list(Rest, [Byte | Acc]);
{error, _} ->
[Byte | Acc]
end;
_ ->
Acc
end
end.
-file("src/aprsme/encoding.gleam", 24).
?DOC(" Convert BitArray to list of bytes\n").
-spec bit_array_to_list(bitstring()) -> list(integer()).
bit_array_to_list(Input) ->
_pipe = do_bit_array_to_list(Input, []),
lists:reverse(_pipe).
-file("src/aprsme/encoding.gleam", 48).
?DOC(" Convert latin1 encoded bytes to UTF-8 string\n").
-spec latin1_to_utf8_string(bitstring()) -> binary().
latin1_to_utf8_string(Input) ->
_pipe = Input,
_pipe@1 = bit_array_to_list(_pipe),
_pipe@2 = gleam@list:filter_map(_pipe@1, fun(Byte) -> case Byte of
B when B =< 127 ->
case gleam@bit_array:to_string(<<B>>) of
{ok, S} ->
{ok, S};
{error, _} ->
{error, nil}
end;
B@1 ->
Byte1 = 192 + (B@1 div 64),
Byte2 = 128 + (B@1 rem 64),
case gleam@bit_array:to_string(<<Byte1, Byte2>>) of
{ok, S@1} ->
{ok, S@1};
{error, _} ->
{error, nil}
end
end end),
gleam@string:join(_pipe@2, <<""/utf8>>).
-file("src/aprsme/encoding.gleam", 77).
?DOC(" Remove control characters from a string\n").
-spec clean_control_characters(binary()) -> binary().
clean_control_characters(S) ->
_pipe = S,
_pipe@1 = gleam@string:to_graphemes(_pipe),
_pipe@2 = gleam@list:filter(
_pipe@1,
fun(Grapheme) -> case gleam@string:to_utf_codepoints(Grapheme) of
[Codepoint] ->
Cp = gleam_stdlib:identity(Codepoint),
case Cp of
9 ->
true;
10 ->
true;
13 ->
true;
C when (C >= 0) andalso (C =< 31) ->
false;
127 ->
false;
C@1 when (C@1 >= 128) andalso (C@1 =< 159) ->
false;
_ ->
true
end;
_ ->
true
end end
),
_pipe@3 = gleam@string:join(_pipe@2, <<""/utf8>>),
gleam@string:trim(_pipe@3).
-file("src/aprsme/encoding.gleam", 10).
?DOC(
" Sanitizes a binary to ensure it can be safely JSON encoded\n"
" Handles latin1 conversion and removes control characters\n"
).
-spec sanitize_string(bitstring()) -> binary().
sanitize_string(Input) ->
case gleam@bit_array:to_string(Input) of
{ok, S} ->
clean_control_characters(S);
{error, _} ->
_pipe = Input,
_pipe@1 = latin1_to_utf8_string(_pipe),
clean_control_characters(_pipe@1)
end.
-file("src/aprsme/encoding.gleam", 104).
?DOC(" Type-safe float conversion with validation\n").
-spec to_float_safe(binary()) -> gleam@option:option(float()).
to_float_safe(Value) ->
Sanitized = begin
_pipe = Value,
_pipe@1 = gleam@string:trim(_pipe),
gleam@string:slice(_pipe@1, 0, 30)
end,
case gleam_stdlib:parse_float(Sanitized) of
{ok, F} ->
case F of
X when (X > -9.0e15) andalso (X < 9.0e15) ->
{some, F};
_ ->
none
end;
{error, _} ->
none
end.
-file("src/aprsme/encoding.gleam", 129).
-spec do_to_hex(bitstring(), list(binary())) -> list(binary()).
do_to_hex(Input, Acc) ->
case gleam_stdlib:bit_array_slice(Input, 0, 1) of
{ok, <<Byte>>} ->
Rest = case gleam_stdlib:bit_array_slice(
Input,
1,
erlang:byte_size(Input) - 1
) of
{ok, R} ->
R;
{error, _} ->
<<>>
end,
Hex = gleam@int:to_base16(Byte),
Padded = case string:length(Hex) of
1 ->
<<"0"/utf8, Hex/binary>>;
_ ->
Hex
end,
do_to_hex(Rest, [Padded | Acc]);
_ ->
Acc
end.
-file("src/aprsme/encoding.gleam", 122).
?DOC(" Convert binary to hex string\n").
-spec to_hex(bitstring()) -> binary().
to_hex(Input) ->
_pipe = do_to_hex(Input, []),
_pipe@1 = lists:reverse(_pipe),
_pipe@2 = gleam@string:join(_pipe@1, <<""/utf8>>),
string:uppercase(_pipe@2).
-file("src/aprsme/encoding.gleam", 148).
?DOC(" Check if a value looks like it has weather data\n").
-spec has_weather_data(
gleam@option:option(float()),
gleam@option:option(float()),
gleam@option:option(float()),
gleam@option:option(float())
) -> boolean().
has_weather_data(Temperature, Humidity, Wind_speed, Pressure) ->
case {Temperature, Humidity, Wind_speed, Pressure} of
{{some, _}, _, _, _} ->
true;
{_, {some, _}, _, _} ->
true;
{_, _, {some, _}, _} ->
true;
{_, _, _, {some, _}} ->
true;
{_, _, _, _} ->
false
end.
-file("src/aprsme/encoding.gleam", 192).
-spec find_invalid_byte_position(bitstring(), integer()) -> gleam@option:option(integer()).
find_invalid_byte_position(Input, Pos) ->
case erlang:byte_size(Input) of
0 ->
none;
_ ->
case gleam_stdlib:bit_array_slice(Input, 0, 1) of
{ok, Byte_slice} ->
case gleam@bit_array:to_string(Byte_slice) of
{ok, _} ->
case gleam_stdlib:bit_array_slice(
Input,
1,
erlang:byte_size(Input) - 1
) of
{ok, Rest} ->
find_invalid_byte_position(Rest, Pos + 1);
{error, _} ->
{some, Pos}
end;
{error, _} ->
{some, Pos}
end;
{error, _} ->
{some, Pos}
end
end.
-file("src/aprsme/encoding.gleam", 170).
?DOC(" Get encoding information about a binary\n").
-spec encoding_info(bitstring()) -> encoding_info().
encoding_info(Input) ->
Byte_count = erlang:byte_size(Input),
case gleam@bit_array:to_string(Input) of
{ok, S} ->
{encoding_info, true, Byte_count, {some, string:length(S)}, none};
{error, _} ->
Invalid_pos = find_invalid_byte_position(Input, 0),
{encoding_info, false, Byte_count, none, Invalid_pos}
end.

View file

@ -0,0 +1,186 @@
import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest';
import {
parseTimestamp,
getTrailId,
saveMapState,
safePushEvent,
isLiveViewConnected,
getLiveSocket
} from '../../../assets/js/map_helpers';
describe('map_helpers', () => {
describe('parseTimestamp', () => {
test('returns current time for null/undefined', () => {
const before = Date.now();
const result = parseTimestamp(null);
const after = Date.now();
expect(result).toBeGreaterThanOrEqual(before);
expect(result).toBeLessThanOrEqual(after);
});
test('returns number timestamp as-is', () => {
const timestamp = 1234567890;
expect(parseTimestamp(timestamp)).toBe(timestamp);
});
test('parses string timestamp', () => {
const dateStr = '2024-01-01T00:00:00Z';
const expected = new Date(dateStr).getTime();
expect(parseTimestamp(dateStr)).toBe(expected);
});
test('returns current time for invalid input', () => {
const before = Date.now();
const result = parseTimestamp({});
const after = Date.now();
expect(result).toBeGreaterThanOrEqual(before);
expect(result).toBeLessThanOrEqual(after);
});
});
describe('getTrailId', () => {
test('prioritizes callsign_group', () => {
const data = {
callsign_group: 'GROUP-1',
callsign: 'CALL-1',
id: 'ID-1'
};
expect(getTrailId(data)).toBe('GROUP-1');
});
test('falls back to callsign when no callsign_group', () => {
const data = {
callsign: 'CALL-1',
id: 'ID-1'
};
expect(getTrailId(data)).toBe('CALL-1');
});
test('falls back to id when no callsign_group or callsign', () => {
const data = {
id: 'ID-1'
};
expect(getTrailId(data)).toBe('ID-1');
});
});
describe('saveMapState', () => {
let mockMap: any;
let mockPushEvent: any;
beforeEach(() => {
// Mock localStorage
const localStorageMock = {
setItem: vi.fn()
};
Object.defineProperty(window, 'localStorage', {
value: localStorageMock,
writable: true
});
// Mock map object
mockMap = {
getCenter: vi.fn().mockReturnValue({ lat: 40.7128, lng: -74.0060 }),
getZoom: vi.fn().mockReturnValue(10),
getBounds: vi.fn().mockReturnValue({
getNorth: vi.fn().mockReturnValue(41.0),
getSouth: vi.fn().mockReturnValue(40.0),
getEast: vi.fn().mockReturnValue(-73.0),
getWest: vi.fn().mockReturnValue(-75.0)
})
};
mockPushEvent = vi.fn();
});
test('saves truncated coordinates to localStorage', () => {
saveMapState(mockMap, mockPushEvent);
expect(localStorage.setItem).toHaveBeenCalledWith(
'aprs_map_state',
JSON.stringify({ lat: 40.7128, lng: -74.006, zoom: 10 })
);
});
test('pushes event with map state and bounds', () => {
saveMapState(mockMap, mockPushEvent);
expect(mockPushEvent).toHaveBeenCalledWith('update_map_state', {
center: { lat: 40.7128, lng: -74.006 },
zoom: 10,
bounds: {
north: 41.0,
south: 40.0,
east: -73.0,
west: -75.0
}
});
});
test('truncates coordinates to 5 decimal places', () => {
mockMap.getCenter.mockReturnValue({ lat: 40.71281234567, lng: -74.00601234567 });
saveMapState(mockMap, mockPushEvent);
const call = mockPushEvent.mock.calls[0][1];
expect(call.center.lat).toBe(40.71281);
expect(call.center.lng).toBe(-74.00601);
});
});
describe('safePushEvent', () => {
test('calls pushEvent and returns true on success', () => {
const mockPushEvent = vi.fn();
const result = safePushEvent(mockPushEvent, 'test_event', { data: 'test' });
expect(mockPushEvent).toHaveBeenCalledWith('test_event', { data: 'test' });
expect(result).toBe(true);
});
test('returns false when pushEvent is undefined', () => {
const result = safePushEvent(undefined, 'test_event', { data: 'test' });
expect(result).toBe(false);
});
test('catches error and returns false', () => {
const mockPushEvent = vi.fn().mockImplementation(() => {
throw new Error('LiveView not connected');
});
const consoleSpy = vi.spyOn(console, 'debug').mockImplementation(() => {});
const result = safePushEvent(mockPushEvent, 'test_event', { data: 'test' });
expect(result).toBe(false);
expect(consoleSpy).toHaveBeenCalledWith('Unable to send test_event event - LiveView disconnected');
consoleSpy.mockRestore();
});
});
describe('LiveView socket helpers', () => {
afterEach(() => {
// Clean up window.liveSocket
delete (window as any).liveSocket;
});
test('isLiveViewConnected returns true when socket exists', () => {
(window as any).liveSocket = { connected: true };
expect(isLiveViewConnected()).toBe(true);
});
test('isLiveViewConnected returns false when socket missing', () => {
expect(isLiveViewConnected()).toBe(false);
});
test('getLiveSocket returns the socket', () => {
const mockSocket = { connected: true, pushHistoryPatch: vi.fn() };
(window as any).liveSocket = mockSocket;
expect(getLiveSocket()).toBe(mockSocket);
});
test('getLiveSocket returns undefined when missing', () => {
expect(getLiveSocket()).toBeUndefined();
});
});
});

View file

@ -0,0 +1,182 @@
#!/usr/bin/env escript
-mode(compile).
% TODO: Don't concurrently print warnings and errors
% TODO: Some tests
main(_) ->
ok = io:setopts([binary, {encoding, utf8}]),
ok = configure_logging(),
compile_package_loop().
compile_package_loop() ->
case io:get_line("") of
eof -> ok;
Line ->
Chars = unicode:characters_to_list(Line),
{ok, Tokens, _} = erl_scan:string(Chars),
{ok, {Lib, Out, Modules}} = erl_parse:parse_term(Tokens),
case compile_package(Lib, Out, Modules) of
{ok, ModuleNames} ->
PrintModuleName = fun(ModuleName) ->
io:put_chars("gleam-compile-module:" ++ atom_to_list(ModuleName) ++ "\n")
end,
lists:map(PrintModuleName, ModuleNames),
io:put_chars("gleam-compile-result-ok\n");
err ->
io:put_chars("gleam-compile-result-error\n")
end,
compile_package_loop()
end.
compile_package(Lib, Out, Modules) ->
IsElixirModule = fun(Module) ->
filename:extension(Module) =:= ".ex"
end,
{ElixirModules, ErlangModules} = lists:partition(IsElixirModule, Modules),
ok = filelib:ensure_dir([Out, $/]),
ok = add_lib_to_erlang_path(Lib),
{ErlangOk, ErlangBeams} = compile_erlang(ErlangModules, Out),
{ElixirOk, ElixirBeams} = case ErlangOk of
true -> compile_elixir(ElixirModules, Out);
false -> {false, []}
end,
ok = del_lib_from_erlang_path(Lib),
case ErlangOk andalso ElixirOk of
true ->
ModuleNames = proplists:get_keys(ErlangBeams ++ ElixirBeams),
{ok, ModuleNames};
false ->
err
end.
compile_erlang(Modules, Out) ->
Workers = start_compiler_workers(Out),
ok = producer_loop(Modules, Workers),
collect_results({true, []}).
collect_results(Acc = {Result, Beams}) ->
receive
{compiled, ModuleName, Beam} -> collect_results({Result, [{ModuleName, Beam} | Beams]});
failed -> collect_results({false, Beams})
after 0 -> Acc
end.
producer_loop([], 0) ->
ok;
producer_loop([], Workers) ->
receive
{work_please, _} -> producer_loop([], Workers - 1)
end;
producer_loop([Module | Modules], Workers) ->
receive
{work_please, Worker} ->
erlang:send(Worker, {module, Module}),
producer_loop(Modules, Workers)
end.
start_compiler_workers(Out) ->
Parent = self(),
NumSchedulers = erlang:system_info(schedulers),
SpawnWorker = fun(_) ->
erlang:spawn_link(fun() -> worker_loop(Parent, Out) end)
end,
lists:foreach(SpawnWorker, lists:seq(1, NumSchedulers)),
NumSchedulers.
worker_loop(Parent, Out) ->
Options = [report_errors, report_warnings, debug_info, {outdir, Out}],
erlang:send(Parent, {work_please, self()}),
receive
{module, Module} ->
log({compiling, Module}),
case compile:file(Module, Options) of
{ok, ModuleName} ->
Beam = filename:join(Out, ModuleName) ++ ".beam",
Message = {compiled, ModuleName, Beam},
log(Message),
erlang:send(Parent, Message);
error ->
log({failed, Module}),
erlang:send(Parent, failed)
end,
worker_loop(Parent, Out)
end.
compile_elixir(Modules, Out) ->
Error = [
"The program elixir was not found. Is it installed?",
$\n,
"Documentation for installing Elixir can be viewed here:",
$\n,
"https://elixir-lang.org/install.html"
],
case Modules of
[] -> {true, []};
_ ->
log({starting, "compiler.app"}),
ok = application:start(compiler),
log({starting, "elixir.app"}),
case application:start(elixir) of
ok -> do_compile_elixir(Modules, Out);
_ ->
io:put_chars(standard_error, [Error, $\n]),
{false, []}
end
end.
do_compile_elixir(Modules, Out) ->
ModuleBins = lists:map(fun(Module) ->
log({compiling, Module}),
list_to_binary(Module)
end, Modules),
OutBin = list_to_binary(Out),
Options = [{dest, OutBin}],
% Silence "redefining module" warnings.
% Compiled modules in the build directory are added to the code path.
% These warnings result from recompiling loaded modules.
% TODO: This line can likely be removed if/when the build directory is cleaned before every compilation.
'Elixir.Code':compiler_options([{ignore_module_conflict, true}]),
case 'Elixir.Kernel.ParallelCompiler':compile_to_path(ModuleBins, OutBin, Options) of
{ok, ModuleAtoms, _} ->
ToBeam = fun(ModuleAtom) ->
Beam = filename:join(Out, atom_to_list(ModuleAtom)) ++ ".beam",
log({compiled, Beam}),
{ModuleAtom, Beam}
end,
{true, lists:map(ToBeam, ModuleAtoms)};
{error, Errors, _} ->
% Log all filenames associated with modules that failed to compile.
% Note: The compiler prints compilation errors upon encountering them.
ErrorFiles = lists:usort([File || {File, _, _} <- Errors]),
Log = fun(File) ->
log({failed, binary_to_list(File)})
end,
lists:foreach(Log, ErrorFiles),
{false, []};
_ -> {false, []}
end.
add_lib_to_erlang_path(Lib) ->
code:add_paths(expand_lib_paths(Lib)).
-if(?OTP_RELEASE >= 26).
del_lib_from_erlang_path(Lib) ->
code:del_paths(expand_lib_paths(Lib)).
-else.
del_lib_from_erlang_path(Lib) ->
lists:foreach(fun code:del_path/1, expand_lib_paths(Lib)).
-endif.
expand_lib_paths(Lib) ->
filelib:wildcard([Lib, "/*/ebin"]).
configure_logging() ->
Enabled = os:getenv("GLEAM_LOG") /= false,
persistent_term:put(gleam_logging_enabled, Enabled).
log(Term) ->
case persistent_term:get(gleam_logging_enabled) of
true -> io:fwrite("~p~n", [Term]), ok;
false -> ok
end.

View file

@ -0,0 +1,167 @@
defmodule AprsIsMock do
@moduledoc """
Mock implementation of Aprs.Is for testing purposes.
This ensures no external APRS connections are made during tests.
"""
use GenServer
require Logger
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@impl true
def init(_opts) do
# Mock connection state
initial_state = %{
connected: false,
server: "mock.aprs.test",
port: 14_580,
connected_at: nil,
login_id: "TEST",
filter: "r/33/-96/100",
packet_stats: %{
total_packets: 0,
last_packet_at: nil,
packets_per_second: 0,
last_second_count: 0,
last_second_timestamp: System.system_time(:second)
},
stored_packet_count: 0
}
{:ok, initial_state}
end
# Client API - Mock implementations
def stop do
GenServer.stop(__MODULE__, :normal)
end
def get_status do
case Process.whereis(__MODULE__) do
nil ->
# Mock disconnected state
%{
connected: false,
server: "mock.aprs.test",
port: 14_580,
connected_at: nil,
uptime_seconds: 0,
login_id: "TEST",
filter: "r/33/-96/100",
packet_stats: %{
total_packets: 0,
last_packet_at: nil,
packets_per_second: 0,
last_second_count: 0,
last_second_timestamp: System.system_time(:second)
},
stored_packet_count: 0
}
_pid ->
try do
GenServer.call(__MODULE__, :get_status, 5000)
catch
:exit, _ ->
# Fallback mock state
%{
connected: false,
server: "mock.aprs.test",
port: 14_580,
connected_at: nil,
uptime_seconds: 0,
login_id: "TEST",
filter: "r/33/-96/100",
packet_stats: %{
total_packets: 0,
last_packet_at: nil,
packets_per_second: 0,
last_second_count: 0,
last_second_timestamp: System.system_time(:second)
},
stored_packet_count: 0
}
end
end
end
def set_filter(_filter_string) do
:ok
end
def list_active_filters do
:ok
end
def send_message(_from, _to, _message) do
:ok
end
def send_message(message) do
GenServer.call(__MODULE__, {:send_message, message})
end
# Server callbacks
@impl true
def handle_call({:send_message, _message}, _from, state) do
{:reply, :ok, state}
end
def handle_call(:get_status, _from, state) do
uptime_seconds =
if state.connected_at do
DateTime.diff(DateTime.utc_now(), state.connected_at, :second)
else
0
end
mock_status =
Map.put(state, :uptime_seconds, uptime_seconds)
{:reply, mock_status, state}
end
def handle_call({:set_connection_state, connected}, _from, state) do
new_state = %{
state
| connected: connected,
connected_at: if(connected, do: DateTime.utc_now())
}
{:reply, :ok, new_state}
end
@impl true
def handle_info(_msg, state) do
{:noreply, state}
end
@impl true
def terminate(_reason, _state) do
:ok
end
# Helper functions for testing
def simulate_packet(packet_data) do
# Simulate receiving an APRS packet for testing purposes.
# This can be used in tests to trigger packet processing without
# connecting to external servers.
# Broadcast to live clients like the real implementation would
AprsmeWeb.Endpoint.broadcast("aprs_messages", "packet", packet_data)
:ok
end
def simulate_connection_state(connected \\ true) do
# Simulate connection state changes for testing.
GenServer.call(__MODULE__, {:set_connection_state, connected})
end
end

View file

@ -0,0 +1,74 @@
defmodule AprsmeWeb.ConnCase do
@moduledoc """
This module defines the test case to be used by
tests that require setting up a connection.
Such tests rely on `Phoenix.ConnTest` and also
import other functionality to make it easier
to build common data structures and query the data layer.
Finally, if the test case interacts with the database,
we enable the SQL sandbox, so changes done to the database
are reverted at the end of every test. If you use PostgreSQL, you
can even run database tests asynchronously by setting
`use AprsmeWeb.ConnCase, async: true`, although
this option is not recommended for other databases.
"""
use ExUnit.CaseTemplate
using do
quote do
# The default import for connections
use AprsmeWeb, :verified_routes
import Aprsme.MockHelpers
import AprsmeWeb.ConnCase
import Phoenix.ConnTest
import Plug.Conn
alias Aprsme.Repo
# The default endpoint for testing
@endpoint AprsmeWeb.Endpoint
# Import conveniences for testing with connections
# The default import for Repo
end
end
setup tags do
Aprsme.DataCase.setup_sandbox(tags)
{:ok, conn: Phoenix.ConnTest.build_conn()}
end
@doc """
A helper that sets up the test case.
use AprsmeWeb.ConnCase, async: true
"""
def setup_sandbox(_tags) do
Aprsme.MockHelpers.stub_packets_mock()
:ok
end
@doc """
A helper that logs in a user.
setup %{conn: conn} do
conn = log_in_user(conn, user)
{:ok, conn: conn}
end
"""
def log_in_user(conn, user) do
token = Aprsme.Accounts.generate_user_session_token(user)
conn
|> Phoenix.ConnTest.init_test_session(%{})
|> Plug.Conn.put_session(:user_token, token)
|> Plug.Conn.put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}")
end
end

View file

@ -0,0 +1,78 @@
defmodule Aprsme.DataCase do
@moduledoc """
This module defines the test case to be used by
data tests.
You may define functions here to be used as helpers in
your data tests. See `errors_on/2`'s definition as an example.
Finally, if the test case interacts with the database,
we enable the SQL sandbox, so changes done to the database
are reverted at the end of every test. If you use PostgreSQL, you
can even run database tests asynchronously by setting
`use Aprsme.DataCase, async: true`, although
this option is not recommended for other databases.
"""
use ExUnit.CaseTemplate
alias Ecto.Adapters.SQL.Sandbox
using do
quote do
import Aprsme.DataCase
# Import conveniences for testing with connections
import Ecto
import Ecto.Changeset
import Ecto.Query
# and other functionality to make calls such as:
# import Aprsme.DataCase
# Aprsme.DataCase.errors_on(MySchema.changeset(%MySchema{}, %{}))
# The default import for Repo
alias Aprsme.Repo
end
end
setup tags do
Aprsme.DataCase.setup_sandbox(tags)
Aprsme.DevicesSeeder.seed_from_json()
:ok
end
@doc """
A helper that transforms changeset errors into a map of messages.
iex> errors_on(MySchema.changeset(%MySchema{}, %{field: bad_value}))
%{field: ["has invalid value"]}
"""
def errors_on(changeset) do
Ecto.Changeset.traverse_errors(changeset, &translate_error/1)
end
@doc """
Sets up the sandbox and allows the test case
to be run asynchronously.
"""
def setup_sandbox(tags) do
pid = Sandbox.start_owner!(Aprsme.Repo, shared: not tags[:async])
on_exit(fn -> Sandbox.stop_owner(pid) end)
end
defp translate_error({msg, opts}) do
# You can make use of gettext to translate error messages by
# uncommenting and adjusting the following code:
# if count = opts[:count] do
# Gettext.dngettext(AprsmeWeb.Gettext, "errors", msg, msg, count, opts)
# else
# Gettext.dgettext(AprsmeWeb.Gettext, "errors", msg, opts)
# end
Enum.reduce(opts, msg, fn {key, value}, acc ->
String.replace(acc, "%{#{key}}", fn _ -> to_string(value) end)
end)
end
end

View file

@ -0,0 +1,37 @@
defmodule Aprsme.AccountsFixtures do
@moduledoc """
This module defines test helpers for creating
entities via the `Aprsme.Accounts` context.
"""
def unique_user_email, do: "user#{System.unique_integer()}@example.com"
def valid_user_password, do: "hello world!"
def unique_user_callsign do
num = rem(System.unique_integer([:positive]), 99) + 1
"K#{num}ABC"
end
def valid_user_attributes(attrs \\ %{}) do
Enum.into(attrs, %{
email: unique_user_email(),
password: valid_user_password(),
callsign: unique_user_callsign()
})
end
def user_fixture(attrs \\ %{}) do
{:ok, user} =
attrs
|> valid_user_attributes()
|> Aprsme.Accounts.register_user()
user
end
def extract_user_token(fun) do
{:ok, captured_email} = fun.(&"[TOKEN]#{&1}[TOKEN]")
[_, token | _] = String.split(captured_email.text_body, "[TOKEN]")
token
end
end

View file

@ -0,0 +1,54 @@
defmodule Aprsme.PacketsFixtures do
@moduledoc """
This module defines test helpers for creating
entities via the `Aprsme.Packets` context.
"""
alias Aprsme.Callsign
alias Aprsme.Packet
alias Aprsme.Repo
@doc """
Generate a packet.
"""
def packet_fixture(attrs \\ %{}) do
base_attrs = %{
sender: "TEST-1",
base_callsign: "TEST",
ssid: "1",
destination: "APRS",
received_at: DateTime.utc_now(),
lat: Decimal.new("40.7128"),
lon: Decimal.new("-74.0060"),
has_position: true,
raw_packet: "TEST-1>APRS:=4042.77N/07400.36W>Test packet",
data_type: "position",
path: "APRS",
data_extended: %{
symbol_table_id: "/",
symbol_code: ">",
comment: "Test packet"
}
}
# Extract base_callsign and ssid from sender if provided
final_attrs =
case Map.get(attrs, :sender) do
nil ->
base_attrs
sender ->
{base, ssid} = Callsign.extract_parts(sender)
Map.merge(base_attrs, %{base_callsign: base, ssid: ssid})
end
{:ok, packet} =
attrs
|> Enum.into(final_attrs)
|> then(&Packet.changeset(%Packet{}, &1))
|> Repo.insert()
packet
end
end

View file

@ -0,0 +1,32 @@
defmodule Aprsme.MockHelpers do
@moduledoc """
Helper functions for setting up mocks in tests.
"""
def stub_packets_mock do
# Stub the packets module to prevent external calls
Mox.stub(Aprsme.PacketsMock, :get_packets_for_callsign, fn _callsign ->
{:ok, []}
end)
Mox.stub(Aprsme.PacketsMock, :get_packets_for_callsign_with_limit, fn _callsign, _limit ->
{:ok, []}
end)
Mox.stub(Aprsme.PacketsMock, :get_packets_for_callsign_with_date_range, fn _callsign, _start_date, _end_date ->
{:ok, []}
end)
Mox.stub(Aprsme.PacketsMock, :get_recent_packets_optimized, fn _opts ->
[]
end)
Mox.stub(Aprsme.PacketsMock, :get_nearby_stations, fn _lat, _lon, _exclude, _opts ->
[]
end)
end
def stub_badpackets_mock do
Mox.stub_with(BadPacketsMock, BadPacketsStub)
end
end

View file

@ -0,0 +1,81 @@
defmodule AprsmeWeb.TestHelpers do
@moduledoc """
Common test helper functions to reduce duplication across test files.
"""
alias Aprsme.Packet
alias Aprsme.Repo
@doc """
Creates a test packet with default values that can be overridden.
"""
def create_test_packet(attrs \\ %{}) do
default_attrs = %{
sender: "TEST-1",
base_callsign: "TEST",
ssid: "1",
lat: Decimal.new("33.0000"),
lon: Decimal.new("-96.0000"),
has_position: true,
received_at: DateTime.truncate(DateTime.utc_now(), :second),
data_type: "position"
}
attrs = Map.merge(default_attrs, attrs)
Repo.insert(%Packet{
sender: attrs.sender,
base_callsign: attrs.base_callsign,
ssid: attrs.ssid,
lat: attrs.lat,
lon: attrs.lon,
has_position: attrs.has_position,
received_at: attrs.received_at,
data_type: attrs.data_type,
symbol_table_id: Map.get(attrs, :symbol_table_id),
symbol_code: Map.get(attrs, :symbol_code),
temperature: Map.get(attrs, :temperature),
humidity: Map.get(attrs, :humidity),
wind_speed: Map.get(attrs, :wind_speed)
})
end
@doc """
Creates common test bounds for Texas area.
"""
def texas_bounds do
%{
"north" => "33.0",
"south" => "32.0",
"east" => "-96.0",
"west" => "-97.0"
}
end
@doc """
Creates common test bounds for a restrictive area.
"""
def restrictive_bounds do
%{
"north" => "31.0",
"south" => "30.0",
"east" => "-95.0",
"west" => "-96.0"
}
end
@doc """
Common time calculations used across tests.
"""
def hours_ago(hours) when is_number(hours) do
DateTime.add(DateTime.utc_now(), -hours * 3600, :second)
end
def minutes_ago(minutes) when is_number(minutes) do
DateTime.add(DateTime.utc_now(), -minutes * 60, :second)
end
def days_ago(days) when is_number(days) do
DateTime.add(DateTime.utc_now(), -days * 86_400, :second)
end
end

View file

@ -0,0 +1,8 @@
{application, aprsme, [
{vsn, "0.2.0"},
{applications, [gleam_stdlib,
gleeunit]},
{description, "APRS packet display with type-safe Gleam modules"},
{modules, []},
{registered, []}
]}.

Binary file not shown.

View file

@ -0,0 +1,6 @@
-record(encoding_info, {
valid_utf8 :: boolean(),
byte_count :: integer(),
char_count :: gleam@option:option(integer()),
invalid_at :: gleam@option:option(integer())
}).

View file

@ -0,0 +1 @@
/Users/graham/dev/aprs.me/priv

1
build/dev/erlang/eex Symbolic link
View file

@ -0,0 +1 @@
/Users/graham/.asdf/installs/elixir/1.19.0-rc.0-otp-27/lib/eex

1
build/dev/erlang/elixir Symbolic link
View file

@ -0,0 +1 @@
/Users/graham/.asdf/installs/elixir/1.19.0-rc.0-otp-27/lib/elixir

View file

@ -0,0 +1,4 @@
/Users/graham/.asdf/installs/elixir/1.19.0-rc.0-otp-27/lib/eex
/Users/graham/.asdf/installs/elixir/1.19.0-rc.0-otp-27/lib/elixir
/Users/graham/.asdf/installs/elixir/1.19.0-rc.0-otp-27/lib/logger
/Users/graham/.asdf/installs/elixir/1.19.0-rc.0-otp-27/lib/mix

View file

@ -0,0 +1,993 @@
/**
* This file uses jsdoc to annotate types.
* These types can be checked using the typescript compiler with "checkjs" option.
*/
import { isEqual } from "./gleam.mjs";
const referenceMap = /* @__PURE__ */ new WeakMap();
const tempDataView = /* @__PURE__ */ new DataView(
/* @__PURE__ */ new ArrayBuffer(8),
);
let referenceUID = 0;
/**
* hash the object by reference using a weak map and incrementing uid
* @param {any} o
* @returns {number}
*/
function hashByReference(o) {
const known = referenceMap.get(o);
if (known !== undefined) {
return known;
}
const hash = referenceUID++;
if (referenceUID === 0x7fffffff) {
referenceUID = 0;
}
referenceMap.set(o, hash);
return hash;
}
/**
* merge two hashes in an order sensitive way
* @param {number} a
* @param {number} b
* @returns {number}
*/
function hashMerge(a, b) {
return (a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2))) | 0;
}
/**
* standard string hash popularised by java
* @param {string} s
* @returns {number}
*/
function hashString(s) {
let hash = 0;
const len = s.length;
for (let i = 0; i < len; i++) {
hash = (Math.imul(31, hash) + s.charCodeAt(i)) | 0;
}
return hash;
}
/**
* hash a number by converting to two integers and do some jumbling
* @param {number} n
* @returns {number}
*/
function hashNumber(n) {
tempDataView.setFloat64(0, n);
const i = tempDataView.getInt32(0);
const j = tempDataView.getInt32(4);
return Math.imul(0x45d9f3b, (i >> 16) ^ i) ^ j;
}
/**
* hash a BigInt by converting it to a string and hashing that
* @param {BigInt} n
* @returns {number}
*/
function hashBigInt(n) {
return hashString(n.toString());
}
/**
* hash any js object
* @param {any} o
* @returns {number}
*/
function hashObject(o) {
const proto = Object.getPrototypeOf(o);
if (proto !== null && typeof proto.hashCode === "function") {
try {
const code = o.hashCode(o);
if (typeof code === "number") {
return code;
}
} catch {}
}
if (o instanceof Promise || o instanceof WeakSet || o instanceof WeakMap) {
return hashByReference(o);
}
if (o instanceof Date) {
return hashNumber(o.getTime());
}
let h = 0;
if (o instanceof ArrayBuffer) {
o = new Uint8Array(o);
}
if (Array.isArray(o) || o instanceof Uint8Array) {
for (let i = 0; i < o.length; i++) {
h = (Math.imul(31, h) + getHash(o[i])) | 0;
}
} else if (o instanceof Set) {
o.forEach((v) => {
h = (h + getHash(v)) | 0;
});
} else if (o instanceof Map) {
o.forEach((v, k) => {
h = (h + hashMerge(getHash(v), getHash(k))) | 0;
});
} else {
const keys = Object.keys(o);
for (let i = 0; i < keys.length; i++) {
const k = keys[i];
const v = o[k];
h = (h + hashMerge(getHash(v), hashString(k))) | 0;
}
}
return h;
}
/**
* hash any js value
* @param {any} u
* @returns {number}
*/
export function getHash(u) {
if (u === null) return 0x42108422;
if (u === undefined) return 0x42108423;
if (u === true) return 0x42108421;
if (u === false) return 0x42108420;
switch (typeof u) {
case "number":
return hashNumber(u);
case "string":
return hashString(u);
case "bigint":
return hashBigInt(u);
case "object":
return hashObject(u);
case "symbol":
return hashByReference(u);
case "function":
return hashByReference(u);
default:
return 0; // should be unreachable
}
}
/**
* @template K,V
* @typedef {ArrayNode<K,V> | IndexNode<K,V> | CollisionNode<K,V>} Node
*/
/**
* @template K,V
* @typedef {{ type: typeof ENTRY, k: K, v: V }} Entry
*/
/**
* @template K,V
* @typedef {{ type: typeof ARRAY_NODE, size: number, array: (undefined | Entry<K,V> | Node<K,V>)[] }} ArrayNode
*/
/**
* @template K,V
* @typedef {{ type: typeof INDEX_NODE, bitmap: number, array: (Entry<K,V> | Node<K,V>)[] }} IndexNode
*/
/**
* @template K,V
* @typedef {{ type: typeof COLLISION_NODE, hash: number, array: Entry<K, V>[] }} CollisionNode
*/
/**
* @typedef {{ val: boolean }} Flag
*/
const SHIFT = 5; // number of bits you need to shift by to get the next bucket
const BUCKET_SIZE = Math.pow(2, SHIFT);
const MASK = BUCKET_SIZE - 1; // used to zero out all bits not in the bucket
const MAX_INDEX_NODE = BUCKET_SIZE / 2; // when does index node grow into array node
const MIN_ARRAY_NODE = BUCKET_SIZE / 4; // when does array node shrink to index node
const ENTRY = 0;
const ARRAY_NODE = 1;
const INDEX_NODE = 2;
const COLLISION_NODE = 3;
/** @type {IndexNode<any,any>} */
const EMPTY = {
type: INDEX_NODE,
bitmap: 0,
array: [],
};
/**
* Mask the hash to get only the bucket corresponding to shift
* @param {number} hash
* @param {number} shift
* @returns {number}
*/
function mask(hash, shift) {
return (hash >>> shift) & MASK;
}
/**
* Set only the Nth bit where N is the masked hash
* @param {number} hash
* @param {number} shift
* @returns {number}
*/
function bitpos(hash, shift) {
return 1 << mask(hash, shift);
}
/**
* Count the number of 1 bits in a number
* @param {number} x
* @returns {number}
*/
function bitcount(x) {
x -= (x >> 1) & 0x55555555;
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
x = (x + (x >> 4)) & 0x0f0f0f0f;
x += x >> 8;
x += x >> 16;
return x & 0x7f;
}
/**
* Calculate the array index of an item in a bitmap index node
* @param {number} bitmap
* @param {number} bit
* @returns {number}
*/
function index(bitmap, bit) {
return bitcount(bitmap & (bit - 1));
}
/**
* Efficiently copy an array and set one value at an index
* @template T
* @param {T[]} arr
* @param {number} at
* @param {T} val
* @returns {T[]}
*/
function cloneAndSet(arr, at, val) {
const len = arr.length;
const out = new Array(len);
for (let i = 0; i < len; ++i) {
out[i] = arr[i];
}
out[at] = val;
return out;
}
/**
* Efficiently copy an array and insert one value at an index
* @template T
* @param {T[]} arr
* @param {number} at
* @param {T} val
* @returns {T[]}
*/
function spliceIn(arr, at, val) {
const len = arr.length;
const out = new Array(len + 1);
let i = 0;
let g = 0;
while (i < at) {
out[g++] = arr[i++];
}
out[g++] = val;
while (i < len) {
out[g++] = arr[i++];
}
return out;
}
/**
* Efficiently copy an array and remove one value at an index
* @template T
* @param {T[]} arr
* @param {number} at
* @returns {T[]}
*/
function spliceOut(arr, at) {
const len = arr.length;
const out = new Array(len - 1);
let i = 0;
let g = 0;
while (i < at) {
out[g++] = arr[i++];
}
++i;
while (i < len) {
out[g++] = arr[i++];
}
return out;
}
/**
* Create a new node containing two entries
* @template K,V
* @param {number} shift
* @param {K} key1
* @param {V} val1
* @param {number} key2hash
* @param {K} key2
* @param {V} val2
* @returns {Node<K,V>}
*/
function createNode(shift, key1, val1, key2hash, key2, val2) {
const key1hash = getHash(key1);
if (key1hash === key2hash) {
return {
type: COLLISION_NODE,
hash: key1hash,
array: [
{ type: ENTRY, k: key1, v: val1 },
{ type: ENTRY, k: key2, v: val2 },
],
};
}
const addedLeaf = { val: false };
return assoc(
assocIndex(EMPTY, shift, key1hash, key1, val1, addedLeaf),
shift,
key2hash,
key2,
val2,
addedLeaf,
);
}
/**
* @template T,K,V
* @callback AssocFunction
* @param {T} root
* @param {number} shift
* @param {number} hash
* @param {K} key
* @param {V} val
* @param {Flag} addedLeaf
* @returns {Node<K,V>}
*/
/**
* Associate a node with a new entry, creating a new node
* @template T,K,V
* @type {AssocFunction<Node<K,V>,K,V>}
*/
function assoc(root, shift, hash, key, val, addedLeaf) {
switch (root.type) {
case ARRAY_NODE:
return assocArray(root, shift, hash, key, val, addedLeaf);
case INDEX_NODE:
return assocIndex(root, shift, hash, key, val, addedLeaf);
case COLLISION_NODE:
return assocCollision(root, shift, hash, key, val, addedLeaf);
}
}
/**
* @template T,K,V
* @type {AssocFunction<ArrayNode<K,V>,K,V>}
*/
function assocArray(root, shift, hash, key, val, addedLeaf) {
const idx = mask(hash, shift);
const node = root.array[idx];
// if the corresponding index is empty set the index to a newly created node
if (node === undefined) {
addedLeaf.val = true;
return {
type: ARRAY_NODE,
size: root.size + 1,
array: cloneAndSet(root.array, idx, { type: ENTRY, k: key, v: val }),
};
}
if (node.type === ENTRY) {
// if keys are equal replace the entry
if (isEqual(key, node.k)) {
if (val === node.v) {
return root;
}
return {
type: ARRAY_NODE,
size: root.size,
array: cloneAndSet(root.array, idx, {
type: ENTRY,
k: key,
v: val,
}),
};
}
// otherwise upgrade the entry to a node and insert
addedLeaf.val = true;
return {
type: ARRAY_NODE,
size: root.size,
array: cloneAndSet(
root.array,
idx,
createNode(shift + SHIFT, node.k, node.v, hash, key, val),
),
};
}
// otherwise call assoc on the child node
const n = assoc(node, shift + SHIFT, hash, key, val, addedLeaf);
// if the child node hasn't changed just return the old root
if (n === node) {
return root;
}
// otherwise set the index to the new node
return {
type: ARRAY_NODE,
size: root.size,
array: cloneAndSet(root.array, idx, n),
};
}
/**
* @template T,K,V
* @type {AssocFunction<IndexNode<K,V>,K,V>}
*/
function assocIndex(root, shift, hash, key, val, addedLeaf) {
const bit = bitpos(hash, shift);
const idx = index(root.bitmap, bit);
// if there is already a item at this hash index..
if ((root.bitmap & bit) !== 0) {
// if there is a node at the index (not an entry), call assoc on the child node
const node = root.array[idx];
if (node.type !== ENTRY) {
const n = assoc(node, shift + SHIFT, hash, key, val, addedLeaf);
if (n === node) {
return root;
}
return {
type: INDEX_NODE,
bitmap: root.bitmap,
array: cloneAndSet(root.array, idx, n),
};
}
// otherwise there is an entry at the index
// if the keys are equal replace the entry with the updated value
const nodeKey = node.k;
if (isEqual(key, nodeKey)) {
if (val === node.v) {
return root;
}
return {
type: INDEX_NODE,
bitmap: root.bitmap,
array: cloneAndSet(root.array, idx, {
type: ENTRY,
k: key,
v: val,
}),
};
}
// if the keys are not equal, replace the entry with a new child node
addedLeaf.val = true;
return {
type: INDEX_NODE,
bitmap: root.bitmap,
array: cloneAndSet(
root.array,
idx,
createNode(shift + SHIFT, nodeKey, node.v, hash, key, val),
),
};
} else {
// else there is currently no item at the hash index
const n = root.array.length;
// if the number of nodes is at the maximum, expand this node into an array node
if (n >= MAX_INDEX_NODE) {
// create a 32 length array for the new array node (one for each bit in the hash)
const nodes = new Array(32);
// create and insert a node for the new entry
const jdx = mask(hash, shift);
nodes[jdx] = assocIndex(EMPTY, shift + SHIFT, hash, key, val, addedLeaf);
let j = 0;
let bitmap = root.bitmap;
// place each item in the index node into the correct spot in the array node
// loop through all 32 bits / array positions
for (let i = 0; i < 32; i++) {
if ((bitmap & 1) !== 0) {
const node = root.array[j++];
nodes[i] = node;
}
// shift the bitmap to process the next bit
bitmap = bitmap >>> 1;
}
return {
type: ARRAY_NODE,
size: n + 1,
array: nodes,
};
} else {
// else there is still space in this index node
// simply insert a new entry at the hash index
const newArray = spliceIn(root.array, idx, {
type: ENTRY,
k: key,
v: val,
});
addedLeaf.val = true;
return {
type: INDEX_NODE,
bitmap: root.bitmap | bit,
array: newArray,
};
}
}
}
/**
* @template T,K,V
* @type {AssocFunction<CollisionNode<K,V>,K,V>}
*/
function assocCollision(root, shift, hash, key, val, addedLeaf) {
// if there is a hash collision
if (hash === root.hash) {
const idx = collisionIndexOf(root, key);
// if this key already exists replace the entry with the new value
if (idx !== -1) {
const entry = root.array[idx];
if (entry.v === val) {
return root;
}
return {
type: COLLISION_NODE,
hash: hash,
array: cloneAndSet(root.array, idx, { type: ENTRY, k: key, v: val }),
};
}
// otherwise insert the entry at the end of the array
const size = root.array.length;
addedLeaf.val = true;
return {
type: COLLISION_NODE,
hash: hash,
array: cloneAndSet(root.array, size, { type: ENTRY, k: key, v: val }),
};
}
// if there is no hash collision, upgrade to an index node
return assoc(
{
type: INDEX_NODE,
bitmap: bitpos(root.hash, shift),
array: [root],
},
shift,
hash,
key,
val,
addedLeaf,
);
}
/**
* Find the index of a key in the collision node's array
* @template K,V
* @param {CollisionNode<K,V>} root
* @param {K} key
* @returns {number}
*/
function collisionIndexOf(root, key) {
const size = root.array.length;
for (let i = 0; i < size; i++) {
if (isEqual(key, root.array[i].k)) {
return i;
}
}
return -1;
}
/**
* @template T,K,V
* @callback FindFunction
* @param {T} root
* @param {number} shift
* @param {number} hash
* @param {K} key
* @returns {undefined | Entry<K,V>}
*/
/**
* Return the found entry or undefined if not present in the root
* @template K,V
* @type {FindFunction<Node<K,V>,K,V>}
*/
function find(root, shift, hash, key) {
switch (root.type) {
case ARRAY_NODE:
return findArray(root, shift, hash, key);
case INDEX_NODE:
return findIndex(root, shift, hash, key);
case COLLISION_NODE:
return findCollision(root, key);
}
}
/**
* @template K,V
* @type {FindFunction<ArrayNode<K,V>,K,V>}
*/
function findArray(root, shift, hash, key) {
const idx = mask(hash, shift);
const node = root.array[idx];
if (node === undefined) {
return undefined;
}
if (node.type !== ENTRY) {
return find(node, shift + SHIFT, hash, key);
}
if (isEqual(key, node.k)) {
return node;
}
return undefined;
}
/**
* @template K,V
* @type {FindFunction<IndexNode<K,V>,K,V>}
*/
function findIndex(root, shift, hash, key) {
const bit = bitpos(hash, shift);
if ((root.bitmap & bit) === 0) {
return undefined;
}
const idx = index(root.bitmap, bit);
const node = root.array[idx];
if (node.type !== ENTRY) {
return find(node, shift + SHIFT, hash, key);
}
if (isEqual(key, node.k)) {
return node;
}
return undefined;
}
/**
* @template K,V
* @param {CollisionNode<K,V>} root
* @param {K} key
* @returns {undefined | Entry<K,V>}
*/
function findCollision(root, key) {
const idx = collisionIndexOf(root, key);
if (idx < 0) {
return undefined;
}
return root.array[idx];
}
/**
* @template T,K,V
* @callback WithoutFunction
* @param {T} root
* @param {number} shift
* @param {number} hash
* @param {K} key
* @returns {undefined | Node<K,V>}
*/
/**
* Remove an entry from the root, returning the updated root.
* Returns undefined if the node should be removed from the parent.
* @template K,V
* @type {WithoutFunction<Node<K,V>,K,V>}
* */
function without(root, shift, hash, key) {
switch (root.type) {
case ARRAY_NODE:
return withoutArray(root, shift, hash, key);
case INDEX_NODE:
return withoutIndex(root, shift, hash, key);
case COLLISION_NODE:
return withoutCollision(root, key);
}
}
/**
* @template K,V
* @type {WithoutFunction<ArrayNode<K,V>,K,V>}
*/
function withoutArray(root, shift, hash, key) {
const idx = mask(hash, shift);
const node = root.array[idx];
if (node === undefined) {
return root; // already empty
}
let n = undefined;
// if node is an entry and the keys are not equal there is nothing to remove
// if node is not an entry do a recursive call
if (node.type === ENTRY) {
if (!isEqual(node.k, key)) {
return root; // no changes
}
} else {
n = without(node, shift + SHIFT, hash, key);
if (n === node) {
return root; // no changes
}
}
// if the recursive call returned undefined the node should be removed
if (n === undefined) {
// if the number of child nodes is at the minimum, pack into an index node
if (root.size <= MIN_ARRAY_NODE) {
const arr = root.array;
const out = new Array(root.size - 1);
let i = 0;
let j = 0;
let bitmap = 0;
while (i < idx) {
const nv = arr[i];
if (nv !== undefined) {
out[j] = nv;
bitmap |= 1 << i;
++j;
}
++i;
}
++i; // skip copying the removed node
while (i < arr.length) {
const nv = arr[i];
if (nv !== undefined) {
out[j] = nv;
bitmap |= 1 << i;
++j;
}
++i;
}
return {
type: INDEX_NODE,
bitmap: bitmap,
array: out,
};
}
return {
type: ARRAY_NODE,
size: root.size - 1,
array: cloneAndSet(root.array, idx, n),
};
}
return {
type: ARRAY_NODE,
size: root.size,
array: cloneAndSet(root.array, idx, n),
};
}
/**
* @template K,V
* @type {WithoutFunction<IndexNode<K,V>,K,V>}
*/
function withoutIndex(root, shift, hash, key) {
const bit = bitpos(hash, shift);
if ((root.bitmap & bit) === 0) {
return root; // already empty
}
const idx = index(root.bitmap, bit);
const node = root.array[idx];
// if the item is not an entry
if (node.type !== ENTRY) {
const n = without(node, shift + SHIFT, hash, key);
if (n === node) {
return root; // no changes
}
// if not undefined, the child node still has items, so update it
if (n !== undefined) {
return {
type: INDEX_NODE,
bitmap: root.bitmap,
array: cloneAndSet(root.array, idx, n),
};
}
// otherwise the child node should be removed
// if it was the only child node, remove this node from the parent
if (root.bitmap === bit) {
return undefined;
}
// otherwise just remove the child node
return {
type: INDEX_NODE,
bitmap: root.bitmap ^ bit,
array: spliceOut(root.array, idx),
};
}
// otherwise the item is an entry, remove it if the key matches
if (isEqual(key, node.k)) {
if (root.bitmap === bit) {
return undefined;
}
return {
type: INDEX_NODE,
bitmap: root.bitmap ^ bit,
array: spliceOut(root.array, idx),
};
}
return root;
}
/**
* @template K,V
* @param {CollisionNode<K,V>} root
* @param {K} key
* @returns {undefined | Node<K,V>}
*/
function withoutCollision(root, key) {
const idx = collisionIndexOf(root, key);
// if the key not found, no changes
if (idx < 0) {
return root;
}
// otherwise the entry was found, remove it
// if it was the only entry in this node, remove the whole node
if (root.array.length === 1) {
return undefined;
}
// otherwise just remove the entry
return {
type: COLLISION_NODE,
hash: root.hash,
array: spliceOut(root.array, idx),
};
}
/**
* @template K,V
* @param {undefined | Node<K,V>} root
* @param {(value:V,key:K)=>void} fn
* @returns {void}
*/
function forEach(root, fn) {
if (root === undefined) {
return;
}
const items = root.array;
const size = items.length;
for (let i = 0; i < size; i++) {
const item = items[i];
if (item === undefined) {
continue;
}
if (item.type === ENTRY) {
fn(item.v, item.k);
continue;
}
forEach(item, fn);
}
}
/**
* Extra wrapper to keep track of Dict size and clean up the API
* @template K,V
*/
export default class Dict {
/**
* @template V
* @param {Record<string,V>} o
* @returns {Dict<string,V>}
*/
static fromObject(o) {
const keys = Object.keys(o);
/** @type Dict<string,V> */
let m = Dict.new();
for (let i = 0; i < keys.length; i++) {
const k = keys[i];
m = m.set(k, o[k]);
}
return m;
}
/**
* @template K,V
* @param {Map<K,V>} o
* @returns {Dict<K,V>}
*/
static fromMap(o) {
/** @type Dict<K,V> */
let m = Dict.new();
o.forEach((v, k) => {
m = m.set(k, v);
});
return m;
}
static new() {
return new Dict(undefined, 0);
}
/**
* @param {undefined | Node<K,V>} root
* @param {number} size
*/
constructor(root, size) {
this.root = root;
this.size = size;
}
/**
* @template NotFound
* @param {K} key
* @param {NotFound} notFound
* @returns {NotFound | V}
*/
get(key, notFound) {
if (this.root === undefined) {
return notFound;
}
const found = find(this.root, 0, getHash(key), key);
if (found === undefined) {
return notFound;
}
return found.v;
}
/**
* @param {K} key
* @param {V} val
* @returns {Dict<K,V>}
*/
set(key, val) {
const addedLeaf = { val: false };
const root = this.root === undefined ? EMPTY : this.root;
const newRoot = assoc(root, 0, getHash(key), key, val, addedLeaf);
if (newRoot === this.root) {
return this;
}
return new Dict(newRoot, addedLeaf.val ? this.size + 1 : this.size);
}
/**
* @param {K} key
* @returns {Dict<K,V>}
*/
delete(key) {
if (this.root === undefined) {
return this;
}
const newRoot = without(this.root, 0, getHash(key), key);
if (newRoot === this.root) {
return this;
}
if (newRoot === undefined) {
return Dict.new();
}
return new Dict(newRoot, this.size - 1);
}
/**
* @param {K} key
* @returns {boolean}
*/
has(key) {
if (this.root === undefined) {
return false;
}
return find(this.root, 0, getHash(key), key) !== undefined;
}
/**
* @returns {[K,V][]}
*/
entries() {
if (this.root === undefined) {
return [];
}
/** @type [K,V][] */
const result = [];
this.forEach((v, k) => result.push([k, v]));
return result;
}
/**
*
* @param {(val:V,key:K)=>void} fn
*/
forEach(fn) {
forEach(this.root, fn);
}
hashCode() {
let h = 0;
this.forEach((v, k) => {
h = (h + hashMerge(getHash(v), getHash(k))) | 0;
});
return h;
}
/**
* @param {unknown} o
* @returns {boolean}
*/
equals(o) {
if (!(o instanceof Dict) || this.size !== o.size) {
return false;
}
try {
this.forEach((v, k) => {
if (!isEqual(o.get(k, !v), v)) {
throw unequalDictSymbol;
}
});
return true;
} catch (e) {
if (e === unequalDictSymbol) {
return false;
}
throw e;
}
}
}
// This is thrown internally in Dict.equals() so that it returns false as soon
// as a non-matching key is found
const unequalDictSymbol = /* @__PURE__ */ Symbol();

View file

@ -0,0 +1,182 @@
#!/usr/bin/env escript
-mode(compile).
% TODO: Don't concurrently print warnings and errors
% TODO: Some tests
main(_) ->
ok = io:setopts([binary, {encoding, utf8}]),
ok = configure_logging(),
compile_package_loop().
compile_package_loop() ->
case io:get_line("") of
eof -> ok;
Line ->
Chars = unicode:characters_to_list(Line),
{ok, Tokens, _} = erl_scan:string(Chars),
{ok, {Lib, Out, Modules}} = erl_parse:parse_term(Tokens),
case compile_package(Lib, Out, Modules) of
{ok, ModuleNames} ->
PrintModuleName = fun(ModuleName) ->
io:put_chars("gleam-compile-module:" ++ atom_to_list(ModuleName) ++ "\n")
end,
lists:map(PrintModuleName, ModuleNames),
io:put_chars("gleam-compile-result-ok\n");
err ->
io:put_chars("gleam-compile-result-error\n")
end,
compile_package_loop()
end.
compile_package(Lib, Out, Modules) ->
IsElixirModule = fun(Module) ->
filename:extension(Module) =:= ".ex"
end,
{ElixirModules, ErlangModules} = lists:partition(IsElixirModule, Modules),
ok = filelib:ensure_dir([Out, $/]),
ok = add_lib_to_erlang_path(Lib),
{ErlangOk, ErlangBeams} = compile_erlang(ErlangModules, Out),
{ElixirOk, ElixirBeams} = case ErlangOk of
true -> compile_elixir(ElixirModules, Out);
false -> {false, []}
end,
ok = del_lib_from_erlang_path(Lib),
case ErlangOk andalso ElixirOk of
true ->
ModuleNames = proplists:get_keys(ErlangBeams ++ ElixirBeams),
{ok, ModuleNames};
false ->
err
end.
compile_erlang(Modules, Out) ->
Workers = start_compiler_workers(Out),
ok = producer_loop(Modules, Workers),
collect_results({true, []}).
collect_results(Acc = {Result, Beams}) ->
receive
{compiled, ModuleName, Beam} -> collect_results({Result, [{ModuleName, Beam} | Beams]});
failed -> collect_results({false, Beams})
after 0 -> Acc
end.
producer_loop([], 0) ->
ok;
producer_loop([], Workers) ->
receive
{work_please, _} -> producer_loop([], Workers - 1)
end;
producer_loop([Module | Modules], Workers) ->
receive
{work_please, Worker} ->
erlang:send(Worker, {module, Module}),
producer_loop(Modules, Workers)
end.
start_compiler_workers(Out) ->
Parent = self(),
NumSchedulers = erlang:system_info(schedulers),
SpawnWorker = fun(_) ->
erlang:spawn_link(fun() -> worker_loop(Parent, Out) end)
end,
lists:foreach(SpawnWorker, lists:seq(1, NumSchedulers)),
NumSchedulers.
worker_loop(Parent, Out) ->
Options = [report_errors, report_warnings, debug_info, {outdir, Out}],
erlang:send(Parent, {work_please, self()}),
receive
{module, Module} ->
log({compiling, Module}),
case compile:file(Module, Options) of
{ok, ModuleName} ->
Beam = filename:join(Out, ModuleName) ++ ".beam",
Message = {compiled, ModuleName, Beam},
log(Message),
erlang:send(Parent, Message);
error ->
log({failed, Module}),
erlang:send(Parent, failed)
end,
worker_loop(Parent, Out)
end.
compile_elixir(Modules, Out) ->
Error = [
"The program elixir was not found. Is it installed?",
$\n,
"Documentation for installing Elixir can be viewed here:",
$\n,
"https://elixir-lang.org/install.html"
],
case Modules of
[] -> {true, []};
_ ->
log({starting, "compiler.app"}),
ok = application:start(compiler),
log({starting, "elixir.app"}),
case application:start(elixir) of
ok -> do_compile_elixir(Modules, Out);
_ ->
io:put_chars(standard_error, [Error, $\n]),
{false, []}
end
end.
do_compile_elixir(Modules, Out) ->
ModuleBins = lists:map(fun(Module) ->
log({compiling, Module}),
list_to_binary(Module)
end, Modules),
OutBin = list_to_binary(Out),
Options = [{dest, OutBin}],
% Silence "redefining module" warnings.
% Compiled modules in the build directory are added to the code path.
% These warnings result from recompiling loaded modules.
% TODO: This line can likely be removed if/when the build directory is cleaned before every compilation.
'Elixir.Code':compiler_options([{ignore_module_conflict, true}]),
case 'Elixir.Kernel.ParallelCompiler':compile_to_path(ModuleBins, OutBin, Options) of
{ok, ModuleAtoms, _} ->
ToBeam = fun(ModuleAtom) ->
Beam = filename:join(Out, atom_to_list(ModuleAtom)) ++ ".beam",
log({compiled, Beam}),
{ModuleAtom, Beam}
end,
{true, lists:map(ToBeam, ModuleAtoms)};
{error, Errors, _} ->
% Log all filenames associated with modules that failed to compile.
% Note: The compiler prints compilation errors upon encountering them.
ErrorFiles = lists:usort([File || {File, _, _} <- Errors]),
Log = fun(File) ->
log({failed, binary_to_list(File)})
end,
lists:foreach(Log, ErrorFiles),
{false, []};
_ -> {false, []}
end.
add_lib_to_erlang_path(Lib) ->
code:add_paths(expand_lib_paths(Lib)).
-if(?OTP_RELEASE >= 26).
del_lib_from_erlang_path(Lib) ->
code:del_paths(expand_lib_paths(Lib)).
-else.
del_lib_from_erlang_path(Lib) ->
lists:foreach(fun code:del_path/1, expand_lib_paths(Lib)).
-endif.
expand_lib_paths(Lib) ->
filelib:wildcard([Lib, "/*/ebin"]).
configure_logging() ->
Enabled = os:getenv("GLEAM_LOG") /= false,
persistent_term:put(gleam_logging_enabled, Enabled).
log(Term) ->
case persistent_term:get(gleam_logging_enabled) of
true -> io:fwrite("~p~n", [Term]), ok;
false -> ok
end.

View file

@ -0,0 +1,346 @@
-module(gleam@bit_array).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/gleam/bit_array.gleam").
-export([from_string/1, bit_size/1, byte_size/1, pad_to_bytes/1, slice/3, is_utf8/1, to_string/1, concat/1, append/2, base64_encode/2, base64_decode/1, base64_url_encode/2, base64_url_decode/1, base16_encode/1, base16_decode/1, inspect/1, compare/2, starts_with/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(" BitArrays are a sequence of binary data of any length.\n").
-file("src/gleam/bit_array.gleam", 11).
?DOC(" Converts a UTF-8 `String` type into a `BitArray`.\n").
-spec from_string(binary()) -> bitstring().
from_string(X) ->
gleam_stdlib:identity(X).
-file("src/gleam/bit_array.gleam", 17).
?DOC(" Returns an integer which is the number of bits in the bit array.\n").
-spec bit_size(bitstring()) -> integer().
bit_size(X) ->
erlang:bit_size(X).
-file("src/gleam/bit_array.gleam", 23).
?DOC(" Returns an integer which is the number of bytes in the bit array.\n").
-spec byte_size(bitstring()) -> integer().
byte_size(X) ->
erlang:byte_size(X).
-file("src/gleam/bit_array.gleam", 29).
?DOC(" Pads a bit array with zeros so that it is a whole number of bytes.\n").
-spec pad_to_bytes(bitstring()) -> bitstring().
pad_to_bytes(X) ->
gleam_stdlib:bit_array_pad_to_bytes(X).
-file("src/gleam/bit_array.gleam", 54).
?DOC(
" Extracts a sub-section of a bit array.\n"
"\n"
" The slice will start at given position and continue up to specified\n"
" length.\n"
" A negative length can be used to extract bytes at the end of a bit array.\n"
"\n"
" This function runs in constant time.\n"
).
-spec slice(bitstring(), integer(), integer()) -> {ok, bitstring()} |
{error, nil}.
slice(String, Position, Length) ->
gleam_stdlib:bit_array_slice(String, Position, Length).
-file("src/gleam/bit_array.gleam", 67).
-spec is_utf8_loop(bitstring()) -> boolean().
is_utf8_loop(Bits) ->
case Bits of
<<>> ->
true;
<<_/utf8, Rest/binary>> ->
is_utf8_loop(Rest);
_ ->
false
end.
-file("src/gleam/bit_array.gleam", 62).
?DOC(" Tests to see whether a bit array is valid UTF-8.\n").
-spec is_utf8(bitstring()) -> boolean().
is_utf8(Bits) ->
is_utf8_loop(Bits).
-file("src/gleam/bit_array.gleam", 88).
?DOC(
" Converts a bit array to a string.\n"
"\n"
" Returns an error if the bit array is invalid UTF-8 data.\n"
).
-spec to_string(bitstring()) -> {ok, binary()} | {error, nil}.
to_string(Bits) ->
case is_utf8(Bits) of
true ->
{ok, gleam_stdlib:identity(Bits)};
false ->
{error, nil}
end.
-file("src/gleam/bit_array.gleam", 109).
?DOC(
" Creates a new bit array by joining multiple binaries.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" concat([from_string(\"butter\"), from_string(\"fly\")])\n"
" // -> from_string(\"butterfly\")\n"
" ```\n"
).
-spec concat(list(bitstring())) -> bitstring().
concat(Bit_arrays) ->
gleam_stdlib:bit_array_concat(Bit_arrays).
-file("src/gleam/bit_array.gleam", 40).
?DOC(
" Creates a new bit array by joining two bit arrays.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" append(to: from_string(\"butter\"), suffix: from_string(\"fly\"))\n"
" // -> from_string(\"butterfly\")\n"
" ```\n"
).
-spec append(bitstring(), bitstring()) -> bitstring().
append(First, Second) ->
gleam_stdlib:bit_array_concat([First, Second]).
-file("src/gleam/bit_array.gleam", 118).
?DOC(
" Encodes a BitArray into a base 64 encoded string.\n"
"\n"
" If the bit array does not contain a whole number of bytes then it is padded\n"
" with zero bits prior to being encoded.\n"
).
-spec base64_encode(bitstring(), boolean()) -> binary().
base64_encode(Input, Padding) ->
gleam_stdlib:bit_array_base64_encode(Input, Padding).
-file("src/gleam/bit_array.gleam", 122).
?DOC(" Decodes a base 64 encoded string into a `BitArray`.\n").
-spec base64_decode(binary()) -> {ok, bitstring()} | {error, nil}.
base64_decode(Encoded) ->
Padded = case erlang:byte_size(gleam_stdlib:identity(Encoded)) rem 4 of
0 ->
Encoded;
N ->
gleam@string:append(
Encoded,
gleam@string:repeat(<<"="/utf8>>, 4 - N)
)
end,
gleam_stdlib:base_decode64(Padded).
-file("src/gleam/bit_array.gleam", 140).
?DOC(
" Encodes a `BitArray` into a base 64 encoded string with URL and filename\n"
" safe alphabet.\n"
"\n"
" If the bit array does not contain a whole number of bytes then it is padded\n"
" with zero bits prior to being encoded.\n"
).
-spec base64_url_encode(bitstring(), boolean()) -> binary().
base64_url_encode(Input, Padding) ->
_pipe = gleam_stdlib:bit_array_base64_encode(Input, Padding),
_pipe@1 = gleam@string:replace(_pipe, <<"+"/utf8>>, <<"-"/utf8>>),
gleam@string:replace(_pipe@1, <<"/"/utf8>>, <<"_"/utf8>>).
-file("src/gleam/bit_array.gleam", 149).
?DOC(
" Decodes a base 64 encoded string with URL and filename safe alphabet into a\n"
" `BitArray`.\n"
).
-spec base64_url_decode(binary()) -> {ok, bitstring()} | {error, nil}.
base64_url_decode(Encoded) ->
_pipe = Encoded,
_pipe@1 = gleam@string:replace(_pipe, <<"-"/utf8>>, <<"+"/utf8>>),
_pipe@2 = gleam@string:replace(_pipe@1, <<"_"/utf8>>, <<"/"/utf8>>),
base64_decode(_pipe@2).
-file("src/gleam/bit_array.gleam", 163).
?DOC(
" Encodes a `BitArray` into a base 16 encoded string.\n"
"\n"
" If the bit array does not contain a whole number of bytes then it is padded\n"
" with zero bits prior to being encoded.\n"
).
-spec base16_encode(bitstring()) -> binary().
base16_encode(Input) ->
gleam_stdlib:base16_encode(Input).
-file("src/gleam/bit_array.gleam", 169).
?DOC(" Decodes a base 16 encoded string into a `BitArray`.\n").
-spec base16_decode(binary()) -> {ok, bitstring()} | {error, nil}.
base16_decode(Input) ->
gleam_stdlib:base16_decode(Input).
-file("src/gleam/bit_array.gleam", 190).
-spec inspect_loop(bitstring(), binary()) -> binary().
inspect_loop(Input, Accumulator) ->
case Input of
<<>> ->
Accumulator;
<<X:1>> ->
<<<<Accumulator/binary, (erlang:integer_to_binary(X))/binary>>/binary,
":size(1)"/utf8>>;
<<X@1:2>> ->
<<<<Accumulator/binary, (erlang:integer_to_binary(X@1))/binary>>/binary,
":size(2)"/utf8>>;
<<X@2:3>> ->
<<<<Accumulator/binary, (erlang:integer_to_binary(X@2))/binary>>/binary,
":size(3)"/utf8>>;
<<X@3:4>> ->
<<<<Accumulator/binary, (erlang:integer_to_binary(X@3))/binary>>/binary,
":size(4)"/utf8>>;
<<X@4:5>> ->
<<<<Accumulator/binary, (erlang:integer_to_binary(X@4))/binary>>/binary,
":size(5)"/utf8>>;
<<X@5:6>> ->
<<<<Accumulator/binary, (erlang:integer_to_binary(X@5))/binary>>/binary,
":size(6)"/utf8>>;
<<X@6:7>> ->
<<<<Accumulator/binary, (erlang:integer_to_binary(X@6))/binary>>/binary,
":size(7)"/utf8>>;
<<X@7, Rest/bitstring>> ->
Suffix = case Rest of
<<>> ->
<<""/utf8>>;
_ ->
<<", "/utf8>>
end,
Accumulator@1 = <<<<Accumulator/binary,
(erlang:integer_to_binary(X@7))/binary>>/binary,
Suffix/binary>>,
inspect_loop(Rest, Accumulator@1);
_ ->
Accumulator
end.
-file("src/gleam/bit_array.gleam", 186).
?DOC(
" Converts a bit array to a string containing the decimal value of each byte.\n"
"\n"
" Use this over `string.inspect` when you have a bit array you want printed\n"
" in the array syntax even if it is valid UTF-8.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" inspect(<<0, 20, 0x20, 255>>)\n"
" // -> \"<<0, 20, 32, 255>>\"\n"
"\n"
" inspect(<<100, 5:3>>)\n"
" // -> \"<<100, 5:size(3)>>\"\n"
" ```\n"
).
-spec inspect(bitstring()) -> binary().
inspect(Input) ->
<<(inspect_loop(Input, <<"<<"/utf8>>))/binary, ">>"/utf8>>.
-file("src/gleam/bit_array.gleam", 231).
?DOC(
" Compare two bit arrays as sequences of bytes.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" compare(<<1>>, <<2>>)\n"
" // -> Lt\n"
"\n"
" compare(<<\"AB\":utf8>>, <<\"AA\":utf8>>)\n"
" // -> Gt\n"
"\n"
" compare(<<1, 2:size(2)>>, with: <<1, 2:size(2)>>)\n"
" // -> Eq\n"
" ```\n"
).
-spec compare(bitstring(), bitstring()) -> gleam@order:order().
compare(A, B) ->
case {A, B} of
{<<First_byte, First_rest/bitstring>>,
<<Second_byte, Second_rest/bitstring>>} ->
case {First_byte, Second_byte} of
{F, S} when F > S ->
gt;
{F@1, S@1} when F@1 < S@1 ->
lt;
{_, _} ->
compare(First_rest, Second_rest)
end;
{<<>>, <<>>} ->
eq;
{_, <<>>} ->
gt;
{<<>>, _} ->
lt;
{First, Second} ->
case {gleam_stdlib:bit_array_to_int_and_size(First),
gleam_stdlib:bit_array_to_int_and_size(Second)} of
{{A@1, _}, {B@1, _}} when A@1 > B@1 ->
gt;
{{A@2, _}, {B@2, _}} when A@2 < B@2 ->
lt;
{{_, Size_a}, {_, Size_b}} when Size_a > Size_b ->
gt;
{{_, Size_a@1}, {_, Size_b@1}} when Size_a@1 < Size_b@1 ->
lt;
{_, _} ->
eq
end
end.
-file("src/gleam/bit_array.gleam", 272).
?DOC(
" Checks whether the first `BitArray` starts with the second one.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" starts_with(<<1, 2, 3, 4>>, <<1, 2>>)\n"
" // -> True\n"
" ```\n"
).
-spec starts_with(bitstring(), bitstring()) -> boolean().
starts_with(Bits, Prefix) ->
Prefix_size = erlang:bit_size(Prefix),
case Bits of
<<Pref:Prefix_size/bitstring, _/bitstring>> when Pref =:= Prefix ->
true;
_ ->
false
end.

View file

@ -0,0 +1,352 @@
-module(gleam@bool).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/gleam/bool.gleam").
-export(['and'/2, 'or'/2, negate/1, nor/2, nand/2, exclusive_or/2, exclusive_nor/2, to_string/1, guard/3, lazy_guard/3]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" A type with two possible values, `True` and `False`. Used to indicate whether\n"
" things are... true or false!\n"
"\n"
" Often is it clearer and offers more type safety to define a custom type\n"
" than to use `Bool`. For example, rather than having a `is_teacher: Bool`\n"
" field consider having a `role: SchoolRole` field where `SchoolRole` is a custom\n"
" type that can be either `Student` or `Teacher`.\n"
).
-file("src/gleam/bool.gleam", 31).
?DOC(
" Returns the and of two bools, but it evaluates both arguments.\n"
"\n"
" It's the function equivalent of the `&&` operator.\n"
" This function is useful in higher order functions or pipes.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" and(True, True)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" and(False, True)\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" False |> and(True)\n"
" // -> False\n"
" ```\n"
).
-spec 'and'(boolean(), boolean()) -> boolean().
'and'(A, B) ->
A andalso B.
-file("src/gleam/bool.gleam", 57).
?DOC(
" Returns the or of two bools, but it evaluates both arguments.\n"
"\n"
" It's the function equivalent of the `||` operator.\n"
" This function is useful in higher order functions or pipes.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" or(True, True)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" or(False, True)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" False |> or(True)\n"
" // -> True\n"
" ```\n"
).
-spec 'or'(boolean(), boolean()) -> boolean().
'or'(A, B) ->
A orelse B.
-file("src/gleam/bool.gleam", 77).
?DOC(
" Returns the opposite bool value.\n"
"\n"
" This is the same as the `!` or `not` operators in some other languages.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" negate(True)\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" negate(False)\n"
" // -> True\n"
" ```\n"
).
-spec negate(boolean()) -> boolean().
negate(Bool) ->
not Bool.
-file("src/gleam/bool.gleam", 105).
?DOC(
" Returns the nor of two bools.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" nor(False, False)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" nor(False, True)\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" nor(True, False)\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" nor(True, True)\n"
" // -> False\n"
" ```\n"
).
-spec nor(boolean(), boolean()) -> boolean().
nor(A, B) ->
not (A orelse B).
-file("src/gleam/bool.gleam", 133).
?DOC(
" Returns the nand of two bools.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" nand(False, False)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" nand(False, True)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" nand(True, False)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" nand(True, True)\n"
" // -> False\n"
" ```\n"
).
-spec nand(boolean(), boolean()) -> boolean().
nand(A, B) ->
not (A andalso B).
-file("src/gleam/bool.gleam", 161).
?DOC(
" Returns the exclusive or of two bools.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" exclusive_or(False, False)\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" exclusive_or(False, True)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" exclusive_or(True, False)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" exclusive_or(True, True)\n"
" // -> False\n"
" ```\n"
).
-spec exclusive_or(boolean(), boolean()) -> boolean().
exclusive_or(A, B) ->
A /= B.
-file("src/gleam/bool.gleam", 189).
?DOC(
" Returns the exclusive nor of two bools.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" exclusive_nor(False, False)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" exclusive_nor(False, True)\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" exclusive_nor(True, False)\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" exclusive_nor(True, True)\n"
" // -> True\n"
" ```\n"
).
-spec exclusive_nor(boolean(), boolean()) -> boolean().
exclusive_nor(A, B) ->
A =:= B.
-file("src/gleam/bool.gleam", 207).
?DOC(
" Returns a string representation of the given bool.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_string(True)\n"
" // -> \"True\"\n"
" ```\n"
"\n"
" ```gleam\n"
" to_string(False)\n"
" // -> \"False\"\n"
" ```\n"
).
-spec to_string(boolean()) -> binary().
to_string(Bool) ->
case Bool of
false ->
<<"False"/utf8>>;
true ->
<<"True"/utf8>>
end.
-file("src/gleam/bool.gleam", 266).
?DOC(
" Run a callback function if the given bool is `False`, otherwise return a\n"
" default value.\n"
"\n"
" With a `use` expression this function can simulate the early-return pattern\n"
" found in some other programming languages.\n"
"\n"
" In a procedural language:\n"
"\n"
" ```js\n"
" if (predicate) return value;\n"
" // ...\n"
" ```\n"
"\n"
" In Gleam with a `use` expression:\n"
"\n"
" ```gleam\n"
" use <- guard(when: predicate, return: value)\n"
" // ...\n"
" ```\n"
"\n"
" Like everything in Gleam `use` is an expression, so it short circuits the\n"
" current block, not the entire function. As a result you can assign the value\n"
" to a variable:\n"
"\n"
" ```gleam\n"
" let x = {\n"
" use <- guard(when: predicate, return: value)\n"
" // ...\n"
" }\n"
" ```\n"
"\n"
" Note that unlike in procedural languages the `return` value is evaluated\n"
" even when the predicate is `False`, so it is advisable not to perform\n"
" expensive computation nor side-effects there.\n"
"\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let name = \"\"\n"
" use <- guard(when: name == \"\", return: \"Welcome!\")\n"
" \"Hello, \" <> name\n"
" // -> \"Welcome!\"\n"
" ```\n"
"\n"
" ```gleam\n"
" let name = \"Kamaka\"\n"
" use <- guard(when: name == \"\", return: \"Welcome!\")\n"
" \"Hello, \" <> name\n"
" // -> \"Hello, Kamaka\"\n"
" ```\n"
).
-spec guard(boolean(), BVF, fun(() -> BVF)) -> BVF.
guard(Requirement, Consequence, Alternative) ->
case Requirement of
true ->
Consequence;
false ->
Alternative()
end.
-file("src/gleam/bool.gleam", 307).
?DOC(
" Runs a callback function if the given bool is `True`, otherwise runs an\n"
" alternative callback function.\n"
"\n"
" Useful when further computation should be delayed regardless of the given\n"
" bool's value.\n"
"\n"
" See [`guard`](#guard) for more info.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let name = \"Kamaka\"\n"
" let inquiry = fn() { \"How may we address you?\" }\n"
" use <- lazy_guard(when: name == \"\", return: inquiry)\n"
" \"Hello, \" <> name\n"
" // -> \"Hello, Kamaka\"\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/int\n"
"\n"
" let name = \"\"\n"
" let greeting = fn() { \"Hello, \" <> name }\n"
" use <- lazy_guard(when: name == \"\", otherwise: greeting)\n"
" let number = int.random(99)\n"
" let name = \"User \" <> int.to_string(number)\n"
" \"Welcome, \" <> name\n"
" // -> \"Welcome, User 54\"\n"
" ```\n"
).
-spec lazy_guard(boolean(), fun(() -> BVG), fun(() -> BVG)) -> BVG.
lazy_guard(Requirement, Consequence, Alternative) ->
case Requirement of
true ->
Consequence();
false ->
Alternative()
end.

View file

@ -0,0 +1,211 @@
-module(gleam@bytes_tree).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/gleam/bytes_tree.gleam").
-export([append_tree/2, prepend_tree/2, concat/1, new/0, from_string/1, prepend_string/2, append_string/2, from_string_tree/1, from_bit_array/1, prepend/2, append/2, concat_bit_arrays/1, to_bit_array/1, byte_size/1]).
-export_type([bytes_tree/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" `BytesTree` is a type used for efficiently building binary content to be\n"
" written to a file or a socket. Internally it is represented as tree so to\n"
" append or prepend to a bytes tree is a constant time operation that\n"
" allocates a new node in the tree without copying any of the content. When\n"
" writing to an output stream the tree is traversed and the content is sent\n"
" directly rather than copying it into a single buffer beforehand.\n"
"\n"
" If we append one bit array to another the bit arrays must be copied to a\n"
" new location in memory so that they can sit together. This behaviour\n"
" enables efficient reading of the data but copying can be expensive,\n"
" especially if we want to join many bit arrays together.\n"
"\n"
" BytesTree is different in that it can be joined together in constant\n"
" time using minimal memory, and then can be efficiently converted to a\n"
" bit array using the `to_bit_array` function.\n"
"\n"
" Byte trees are always byte aligned, so that a number of bits that is not\n"
" divisible by 8 will be padded with 0s.\n"
"\n"
" On Erlang this type is compatible with Erlang's iolists.\n"
).
-opaque bytes_tree() :: {bytes, bitstring()} |
{text, gleam@string_tree:string_tree()} |
{many, list(bytes_tree())}.
-file("src/gleam/bytes_tree.gleam", 68).
?DOC(
" Appends a bytes tree onto the end of another.\n"
"\n"
" Runs in constant time.\n"
).
-spec append_tree(bytes_tree(), bytes_tree()) -> bytes_tree().
append_tree(First, Second) ->
gleam_stdlib:iodata_append(First, Second).
-file("src/gleam/bytes_tree.gleam", 59).
?DOC(
" Prepends a bytes tree onto the start of another.\n"
"\n"
" Runs in constant time.\n"
).
-spec prepend_tree(bytes_tree(), bytes_tree()) -> bytes_tree().
prepend_tree(Second, First) ->
gleam_stdlib:iodata_append(First, Second).
-file("src/gleam/bytes_tree.gleam", 98).
?DOC(
" Joins a list of bytes trees into a single one.\n"
"\n"
" Runs in constant time.\n"
).
-spec concat(list(bytes_tree())) -> bytes_tree().
concat(Trees) ->
gleam_stdlib:identity(Trees).
-file("src/gleam/bytes_tree.gleam", 35).
?DOC(
" Create an empty `BytesTree`. Useful as the start of a pipe chaining many\n"
" trees together.\n"
).
-spec new() -> bytes_tree().
new() ->
gleam_stdlib:identity([]).
-file("src/gleam/bytes_tree.gleam", 118).
?DOC(
" Creates a new bytes tree from a string.\n"
"\n"
" Runs in constant time when running on Erlang.\n"
" Runs in linear time otherwise.\n"
).
-spec from_string(binary()) -> bytes_tree().
from_string(String) ->
gleam_stdlib:wrap_list(String).
-file("src/gleam/bytes_tree.gleam", 80).
?DOC(
" Prepends a string onto the start of a bytes tree.\n"
"\n"
" Runs in constant time when running on Erlang.\n"
" Runs in linear time with the length of the string otherwise.\n"
).
-spec prepend_string(bytes_tree(), binary()) -> bytes_tree().
prepend_string(Second, First) ->
gleam_stdlib:iodata_append(gleam_stdlib:wrap_list(First), Second).
-file("src/gleam/bytes_tree.gleam", 89).
?DOC(
" Appends a string onto the end of a bytes tree.\n"
"\n"
" Runs in constant time when running on Erlang.\n"
" Runs in linear time with the length of the string otherwise.\n"
).
-spec append_string(bytes_tree(), binary()) -> bytes_tree().
append_string(First, Second) ->
gleam_stdlib:iodata_append(First, gleam_stdlib:wrap_list(Second)).
-file("src/gleam/bytes_tree.gleam", 128).
?DOC(
" Creates a new bytes tree from a string tree.\n"
"\n"
" Runs in constant time when running on Erlang.\n"
" Runs in linear time otherwise.\n"
).
-spec from_string_tree(gleam@string_tree:string_tree()) -> bytes_tree().
from_string_tree(Tree) ->
gleam_stdlib:wrap_list(Tree).
-file("src/gleam/bytes_tree.gleam", 136).
?DOC(
" Creates a new bytes tree from a bit array.\n"
"\n"
" Runs in constant time.\n"
).
-spec from_bit_array(bitstring()) -> bytes_tree().
from_bit_array(Bits) ->
_pipe = Bits,
_pipe@1 = gleam_stdlib:bit_array_pad_to_bytes(_pipe),
gleam_stdlib:wrap_list(_pipe@1).
-file("src/gleam/bytes_tree.gleam", 43).
?DOC(
" Prepends a bit array to the start of a bytes tree.\n"
"\n"
" Runs in constant time.\n"
).
-spec prepend(bytes_tree(), bitstring()) -> bytes_tree().
prepend(Second, First) ->
gleam_stdlib:iodata_append(from_bit_array(First), Second).
-file("src/gleam/bytes_tree.gleam", 51).
?DOC(
" Appends a bit array to the end of a bytes tree.\n"
"\n"
" Runs in constant time.\n"
).
-spec append(bytes_tree(), bitstring()) -> bytes_tree().
append(First, Second) ->
gleam_stdlib:iodata_append(First, from_bit_array(Second)).
-file("src/gleam/bytes_tree.gleam", 106).
?DOC(
" Joins a list of bit arrays into a single bytes tree.\n"
"\n"
" Runs in constant time.\n"
).
-spec concat_bit_arrays(list(bitstring())) -> bytes_tree().
concat_bit_arrays(Bits) ->
_pipe = Bits,
_pipe@1 = gleam@list:map(_pipe, fun(B) -> from_bit_array(B) end),
gleam_stdlib:identity(_pipe@1).
-file("src/gleam/bytes_tree.gleam", 162).
-spec to_list(list(list(bytes_tree())), list(bitstring())) -> list(bitstring()).
to_list(Stack, Acc) ->
case Stack of
[] ->
Acc;
[[] | Remaining_stack] ->
to_list(Remaining_stack, Acc);
[[{bytes, Bits} | Rest] | Remaining_stack@1] ->
to_list([Rest | Remaining_stack@1], [Bits | Acc]);
[[{text, Tree} | Rest@1] | Remaining_stack@2] ->
Bits@1 = gleam_stdlib:identity(unicode:characters_to_binary(Tree)),
to_list([Rest@1 | Remaining_stack@2], [Bits@1 | Acc]);
[[{many, Trees} | Rest@2] | Remaining_stack@3] ->
to_list([Trees, Rest@2 | Remaining_stack@3], Acc)
end.
-file("src/gleam/bytes_tree.gleam", 155).
?DOC(
" Turns a bytes tree into a bit array.\n"
"\n"
" Runs in linear time.\n"
"\n"
" When running on Erlang this function is implemented natively by the\n"
" virtual machine and is highly optimised.\n"
).
-spec to_bit_array(bytes_tree()) -> bitstring().
to_bit_array(Tree) ->
erlang:list_to_bitstring(Tree).
-file("src/gleam/bytes_tree.gleam", 186).
?DOC(
" Returns the size of the bytes tree's content in bytes.\n"
"\n"
" Runs in linear time.\n"
).
-spec byte_size(bytes_tree()) -> integer().
byte_size(Tree) ->
erlang:iolist_size(Tree).

View file

@ -0,0 +1,561 @@
-module(gleam@dict).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/gleam/dict.gleam").
-export([size/1, is_empty/1, to_list/1, new/0, get/2, has_key/2, insert/3, from_list/1, keys/1, values/1, take/2, merge/2, delete/2, drop/2, upsert/3, fold/3, map_values/2, filter/2, each/2, combine/3]).
-export_type([dict/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type dict(LA, LB) :: any() | {gleam_phantom, LA, LB}.
-file("src/gleam/dict.gleam", 36).
?DOC(
" Determines the number of key-value pairs in the dict.\n"
" This function runs in constant time and does not need to iterate the dict.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new() |> size\n"
" // -> 0\n"
" ```\n"
"\n"
" ```gleam\n"
" new() |> insert(\"key\", \"value\") |> size\n"
" // -> 1\n"
" ```\n"
).
-spec size(dict(any(), any())) -> integer().
size(Dict) ->
maps:size(Dict).
-file("src/gleam/dict.gleam", 52).
?DOC(
" Determines whether or not the dict is empty.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new() |> is_empty\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" new() |> insert(\"b\", 1) |> is_empty\n"
" // -> False\n"
" ```\n"
).
-spec is_empty(dict(any(), any())) -> boolean().
is_empty(Dict) ->
maps:size(Dict) =:= 0.
-file("src/gleam/dict.gleam", 80).
?DOC(
" Converts the dict to a list of 2-element tuples `#(key, value)`, one for\n"
" each key-value pair in the dict.\n"
"\n"
" The tuples in the list have no specific order.\n"
"\n"
" ## Examples\n"
"\n"
" Calling `to_list` on an empty `dict` returns an empty list.\n"
"\n"
" ```gleam\n"
" new() |> to_list\n"
" // -> []\n"
" ```\n"
"\n"
" The ordering of elements in the resulting list is an implementation detail\n"
" that should not be relied upon.\n"
"\n"
" ```gleam\n"
" new() |> insert(\"b\", 1) |> insert(\"a\", 0) |> insert(\"c\", 2) |> to_list\n"
" // -> [#(\"a\", 0), #(\"b\", 1), #(\"c\", 2)]\n"
" ```\n"
).
-spec to_list(dict(LK, LL)) -> list({LK, LL}).
to_list(Dict) ->
maps:to_list(Dict).
-file("src/gleam/dict.gleam", 129).
?DOC(" Creates a fresh dict that contains no values.\n").
-spec new() -> dict(any(), any()).
new() ->
maps:new().
-file("src/gleam/dict.gleam", 150).
?DOC(
" Fetches a value from a dict for a given key.\n"
"\n"
" The dict may not have a value for the key, so the value is wrapped in a\n"
" `Result`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new() |> insert(\"a\", 0) |> get(\"a\")\n"
" // -> Ok(0)\n"
" ```\n"
"\n"
" ```gleam\n"
" new() |> insert(\"a\", 0) |> get(\"b\")\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec get(dict(MN, MO), MN) -> {ok, MO} | {error, nil}.
get(From, Get) ->
gleam_stdlib:map_get(From, Get).
-file("src/gleam/dict.gleam", 116).
?DOC(
" Determines whether or not a value present in the dict for a given key.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new() |> insert(\"a\", 0) |> has_key(\"a\")\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" new() |> insert(\"a\", 0) |> has_key(\"b\")\n"
" // -> False\n"
" ```\n"
).
-spec has_key(dict(MB, any()), MB) -> boolean().
has_key(Dict, Key) ->
maps:is_key(Key, Dict).
-file("src/gleam/dict.gleam", 169).
?DOC(
" Inserts a value into the dict with the given key.\n"
"\n"
" If the dict already has a value for the given key then the value is\n"
" replaced with the new value.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new() |> insert(\"a\", 0)\n"
" // -> from_list([#(\"a\", 0)])\n"
" ```\n"
"\n"
" ```gleam\n"
" new() |> insert(\"a\", 0) |> insert(\"a\", 5)\n"
" // -> from_list([#(\"a\", 5)])\n"
" ```\n"
).
-spec insert(dict(MT, MU), MT, MU) -> dict(MT, MU).
insert(Dict, Key, Value) ->
maps:put(Key, Value, Dict).
-file("src/gleam/dict.gleam", 92).
-spec from_list_loop(list({LU, LV}), dict(LU, LV)) -> dict(LU, LV).
from_list_loop(List, Initial) ->
case List of
[] ->
Initial;
[{Key, Value} | Rest] ->
from_list_loop(Rest, insert(Initial, Key, Value))
end.
-file("src/gleam/dict.gleam", 88).
?DOC(
" Converts a list of 2-element tuples `#(key, value)` to a dict.\n"
"\n"
" If two tuples have the same key the last one in the list will be the one\n"
" that is present in the dict.\n"
).
-spec from_list(list({LP, LQ})) -> dict(LP, LQ).
from_list(List) ->
maps:from_list(List).
-file("src/gleam/dict.gleam", 223).
-spec reverse_and_concat(list(OD), list(OD)) -> list(OD).
reverse_and_concat(Remaining, Accumulator) ->
case Remaining of
[] ->
Accumulator;
[First | Rest] ->
reverse_and_concat(Rest, [First | Accumulator])
end.
-file("src/gleam/dict.gleam", 216).
-spec do_keys_loop(list({NY, any()}), list(NY)) -> list(NY).
do_keys_loop(List, Acc) ->
case List of
[] ->
reverse_and_concat(Acc, []);
[{Key, _} | Rest] ->
do_keys_loop(Rest, [Key | Acc])
end.
-file("src/gleam/dict.gleam", 212).
?DOC(
" Gets a list of all keys in a given dict.\n"
"\n"
" Dicts are not ordered so the keys are not returned in any specific order. Do\n"
" not write code that relies on the order keys are returned by this function\n"
" as it may change in later versions of Gleam or Erlang.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)]) |> keys\n"
" // -> [\"a\", \"b\"]\n"
" ```\n"
).
-spec keys(dict(NT, any())) -> list(NT).
keys(Dict) ->
maps:keys(Dict).
-file("src/gleam/dict.gleam", 249).
-spec do_values_loop(list({any(), ON}), list(ON)) -> list(ON).
do_values_loop(List, Acc) ->
case List of
[] ->
reverse_and_concat(Acc, []);
[{_, Value} | Rest] ->
do_values_loop(Rest, [Value | Acc])
end.
-file("src/gleam/dict.gleam", 244).
?DOC(
" Gets a list of all values in a given dict.\n"
"\n"
" Dicts are not ordered so the values are not returned in any specific order. Do\n"
" not write code that relies on the order values are returned by this function\n"
" as it may change in later versions of Gleam or Erlang.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)]) |> values\n"
" // -> [0, 1]\n"
" ```\n"
).
-spec values(dict(any(), OI)) -> list(OI).
values(Dict) ->
maps:values(Dict).
-file("src/gleam/dict.gleam", 318).
-spec do_take_loop(dict(PR, PS), list(PR), dict(PR, PS)) -> dict(PR, PS).
do_take_loop(Dict, Desired_keys, Acc) ->
Insert = fun(Taken, Key) -> case gleam_stdlib:map_get(Dict, Key) of
{ok, Value} ->
insert(Taken, Key, Value);
{error, _} ->
Taken
end end,
case Desired_keys of
[] ->
Acc;
[First | Rest] ->
do_take_loop(Dict, Rest, Insert(Acc, First))
end.
-file("src/gleam/dict.gleam", 309).
?DOC(
" Creates a new dict from a given dict, only including any entries for which the\n"
" keys are in a given list.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" |> take([\"b\"])\n"
" // -> from_list([#(\"b\", 1)])\n"
" ```\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" |> take([\"a\", \"b\", \"c\"])\n"
" // -> from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" ```\n"
).
-spec take(dict(PD, PE), list(PD)) -> dict(PD, PE).
take(Dict, Desired_keys) ->
maps:with(Desired_keys, Dict).
-file("src/gleam/dict.gleam", 363).
-spec insert_pair(dict(QP, QQ), {QP, QQ}) -> dict(QP, QQ).
insert_pair(Dict, Pair) ->
insert(Dict, erlang:element(1, Pair), erlang:element(2, Pair)).
-file("src/gleam/dict.gleam", 356).
-spec fold_inserts(list({QI, QJ}), dict(QI, QJ)) -> dict(QI, QJ).
fold_inserts(New_entries, Dict) ->
case New_entries of
[] ->
Dict;
[First | Rest] ->
fold_inserts(Rest, insert_pair(Dict, First))
end.
-file("src/gleam/dict.gleam", 350).
?DOC(
" Creates a new dict from a pair of given dicts by combining their entries.\n"
"\n"
" If there are entries with the same keys in both dicts the entry from the\n"
" second dict takes precedence.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let a = from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" let b = from_list([#(\"b\", 2), #(\"c\", 3)])\n"
" merge(a, b)\n"
" // -> from_list([#(\"a\", 0), #(\"b\", 2), #(\"c\", 3)])\n"
" ```\n"
).
-spec merge(dict(QA, QB), dict(QA, QB)) -> dict(QA, QB).
merge(Dict, New_entries) ->
maps:merge(Dict, New_entries).
-file("src/gleam/dict.gleam", 382).
?DOC(
" Creates a new dict from a given dict with all the same entries except for the\n"
" one with a given key, if it exists.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)]) |> delete(\"a\")\n"
" // -> from_list([#(\"b\", 1)])\n"
" ```\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)]) |> delete(\"c\")\n"
" // -> from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" ```\n"
).
-spec delete(dict(QV, QW), QV) -> dict(QV, QW).
delete(Dict, Key) ->
maps:remove(Key, Dict).
-file("src/gleam/dict.gleam", 410).
?DOC(
" Creates a new dict from a given dict with all the same entries except any with\n"
" keys found in a given list.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)]) |> drop([\"a\"])\n"
" // -> from_list([#(\"b\", 1)])\n"
" ```\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)]) |> drop([\"c\"])\n"
" // -> from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" ```\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)]) |> drop([\"a\", \"b\", \"c\"])\n"
" // -> from_list([])\n"
" ```\n"
).
-spec drop(dict(RH, RI), list(RH)) -> dict(RH, RI).
drop(Dict, Disallowed_keys) ->
case Disallowed_keys of
[] ->
Dict;
[First | Rest] ->
drop(delete(Dict, First), Rest)
end.
-file("src/gleam/dict.gleam", 440).
?DOC(
" Creates a new dict with one entry inserted or updated using a given function.\n"
"\n"
" If there was not an entry in the dict for the given key then the function\n"
" gets `None` as its argument, otherwise it gets `Some(value)`.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let dict = from_list([#(\"a\", 0)])\n"
" let increment = fn(x) {\n"
" case x {\n"
" Some(i) -> i + 1\n"
" None -> 0\n"
" }\n"
" }\n"
"\n"
" upsert(dict, \"a\", increment)\n"
" // -> from_list([#(\"a\", 1)])\n"
"\n"
" upsert(dict, \"b\", increment)\n"
" // -> from_list([#(\"a\", 0), #(\"b\", 0)])\n"
" ```\n"
).
-spec upsert(dict(RO, RP), RO, fun((gleam@option:option(RP)) -> RP)) -> dict(RO, RP).
upsert(Dict, Key, Fun) ->
case gleam_stdlib:map_get(Dict, Key) of
{ok, Value} ->
insert(Dict, Key, Fun({some, Value}));
{error, _} ->
insert(Dict, Key, Fun(none))
end.
-file("src/gleam/dict.gleam", 484).
-spec fold_loop(list({SA, SB}), SD, fun((SD, SA, SB) -> SD)) -> SD.
fold_loop(List, Initial, Fun) ->
case List of
[] ->
Initial;
[{K, V} | Rest] ->
fold_loop(Rest, Fun(Initial, K, V), Fun)
end.
-file("src/gleam/dict.gleam", 476).
?DOC(
" Combines all entries into a single value by calling a given function on each\n"
" one.\n"
"\n"
" Dicts are not ordered so the values are not returned in any specific order. Do\n"
" not write code that relies on the order entries are used by this function\n"
" as it may change in later versions of Gleam or Erlang.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" let dict = from_list([#(\"a\", 1), #(\"b\", 3), #(\"c\", 9)])\n"
" fold(dict, 0, fn(accumulator, key, value) { accumulator + value })\n"
" // -> 13\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/string\n"
"\n"
" let dict = from_list([#(\"a\", 1), #(\"b\", 3), #(\"c\", 9)])\n"
" fold(dict, \"\", fn(accumulator, key, value) {\n"
" string.append(accumulator, key)\n"
" })\n"
" // -> \"abc\"\n"
" ```\n"
).
-spec fold(dict(RV, RW), RZ, fun((RZ, RV, RW) -> RZ)) -> RZ.
fold(Dict, Initial, Fun) ->
fold_loop(maps:to_list(Dict), Initial, Fun).
-file("src/gleam/dict.gleam", 188).
?DOC(
" Updates all values in a given dict by calling a given function on each key\n"
" and value.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([#(3, 3), #(2, 4)])\n"
" |> map_values(fn(key, value) { key * value })\n"
" // -> from_list([#(3, 9), #(2, 8)])\n"
" ```\n"
).
-spec map_values(dict(NF, NG), fun((NF, NG) -> NJ)) -> dict(NF, NJ).
map_values(Dict, Fun) ->
maps:map(Fun, Dict).
-file("src/gleam/dict.gleam", 273).
?DOC(
" Creates a new dict from a given dict, minus any entries that a given function\n"
" returns `False` for.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" |> filter(fn(key, value) { value != 0 })\n"
" // -> from_list([#(\"b\", 1)])\n"
" ```\n"
"\n"
" ```gleam\n"
" from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" |> filter(fn(key, value) { True })\n"
" // -> from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" ```\n"
).
-spec filter(dict(OR, OS), fun((OR, OS) -> boolean())) -> dict(OR, OS).
filter(Dict, Predicate) ->
maps:filter(Predicate, Dict).
-file("src/gleam/dict.gleam", 517).
?DOC(
" Calls a function for each key and value in a dict, discarding the return\n"
" value.\n"
"\n"
" Useful for producing a side effect for every item of a dict.\n"
"\n"
" ```gleam\n"
" import gleam/io\n"
"\n"
" let dict = from_list([#(\"a\", \"apple\"), #(\"b\", \"banana\"), #(\"c\", \"cherry\")])\n"
"\n"
" each(dict, fn(k, v) {\n"
" io.println(key <> \" => \" <> value)\n"
" })\n"
" // -> Nil\n"
" // a => apple\n"
" // b => banana\n"
" // c => cherry\n"
" ```\n"
"\n"
" The order of elements in the iteration is an implementation detail that\n"
" should not be relied upon.\n"
).
-spec each(dict(SE, SF), fun((SE, SF) -> any())) -> nil.
each(Dict, Fun) ->
fold(
Dict,
nil,
fun(Nil, K, V) ->
Fun(K, V),
Nil
end
).
-file("src/gleam/dict.gleam", 538).
?DOC(
" Creates a new dict from a pair of given dicts by combining their entries.\n"
"\n"
" If there are entries with the same keys in both dicts the given function is\n"
" used to determine the new value to use in the resulting dict.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let a = from_list([#(\"a\", 0), #(\"b\", 1)])\n"
" let b = from_list([#(\"a\", 2), #(\"c\", 3)])\n"
" combine(a, b, fn(one, other) { one + other })\n"
" // -> from_list([#(\"a\", 2), #(\"b\", 1), #(\"c\", 3)])\n"
" ```\n"
).
-spec combine(dict(SJ, SK), dict(SJ, SK), fun((SK, SK) -> SK)) -> dict(SJ, SK).
combine(Dict, Other, Fun) ->
fold(
Dict,
Other,
fun(Acc, Key, Value) -> case gleam_stdlib:map_get(Acc, Key) of
{ok, Other_value} ->
insert(Acc, Key, Fun(Value, Other_value));
{error, _} ->
insert(Acc, Key, Value)
end end
).

View file

@ -0,0 +1,106 @@
-module(gleam@dynamic).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/gleam/dynamic.gleam").
-export([classify/1, bool/1, string/1, float/1, int/1, bit_array/1, list/1, array/1, properties/1, nil/0]).
-export_type([dynamic_/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type dynamic_() :: any().
-file("src/gleam/dynamic.gleam", 30).
?DOC(
" Return a string indicating the type of the dynamic value.\n"
"\n"
" This function may be useful for constructing error messages or logs. If you\n"
" want to turn dynamic data into well typed data then you want the\n"
" `gleam/dynamic/decode` module.\n"
"\n"
" ```gleam\n"
" classify(from(\"Hello\"))\n"
" // -> \"String\"\n"
" ```\n"
).
-spec classify(dynamic_()) -> binary().
classify(Data) ->
gleam_stdlib:classify_dynamic(Data).
-file("src/gleam/dynamic.gleam", 36).
?DOC(" Create a dynamic value from a bool.\n").
-spec bool(boolean()) -> dynamic_().
bool(A) ->
gleam_stdlib:identity(A).
-file("src/gleam/dynamic.gleam", 44).
?DOC(
" Create a dynamic value from a string.\n"
"\n"
" On Erlang this will be a binary string rather than a character list.\n"
).
-spec string(binary()) -> dynamic_().
string(A) ->
gleam_stdlib:identity(A).
-file("src/gleam/dynamic.gleam", 50).
?DOC(" Create a dynamic value from a float.\n").
-spec float(float()) -> dynamic_().
float(A) ->
gleam_stdlib:identity(A).
-file("src/gleam/dynamic.gleam", 56).
?DOC(" Create a dynamic value from an int.\n").
-spec int(integer()) -> dynamic_().
int(A) ->
gleam_stdlib:identity(A).
-file("src/gleam/dynamic.gleam", 62).
?DOC(" Create a dynamic value from a bit array.\n").
-spec bit_array(bitstring()) -> dynamic_().
bit_array(A) ->
gleam_stdlib:identity(A).
-file("src/gleam/dynamic.gleam", 68).
?DOC(" Create a dynamic value from a list.\n").
-spec list(list(dynamic_())) -> dynamic_().
list(A) ->
gleam_stdlib:identity(A).
-file("src/gleam/dynamic.gleam", 77).
?DOC(
" Create a dynamic value from a list, converting it to a sequential runtime\n"
" format rather than the regular list format.\n"
"\n"
" On Erlang this will be a tuple, on JavaScript this will be an array.\n"
).
-spec array(list(dynamic_())) -> dynamic_().
array(A) ->
erlang:list_to_tuple(A).
-file("src/gleam/dynamic.gleam", 85).
?DOC(
" Create a dynamic value made an unordered series of keys and values, where\n"
" the keys are unique.\n"
"\n"
" On Erlang this will be a map, on JavaScript this will be a Gleam dict\n"
" object.\n"
).
-spec properties(list({dynamic_(), dynamic_()})) -> dynamic_().
properties(Entries) ->
gleam_stdlib:identity(maps:from_list(Entries)).
-file("src/gleam/dynamic.gleam", 94).
?DOC(
" A dynamic value representing nothing.\n"
"\n"
" On Erlang this will be the atom `nil`, on JavaScript this will be\n"
" `undefined`.\n"
).
-spec nil() -> dynamic_().
nil() ->
gleam_stdlib:identity(nil).

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,744 @@
-module(gleam@float).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/gleam/float.gleam").
-export([parse/1, to_string/1, compare/2, min/2, max/2, clamp/3, ceiling/1, floor/1, truncate/1, absolute_value/1, loosely_compare/3, loosely_equals/3, power/2, square_root/1, negate/1, round/1, to_precision/2, sum/1, product/1, random/0, modulo/2, divide/2, add/2, multiply/2, subtract/2, logarithm/1, exponential/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Functions for working with floats.\n"
"\n"
" ## Float representation\n"
"\n"
" Floats are represented as 64 bit floating point numbers on both the Erlang\n"
" and JavaScript runtimes. The floating point behaviour is native to their\n"
" respective runtimes, so their exact behaviour will be slightly different on\n"
" the two runtimes.\n"
"\n"
" ### Infinity and NaN\n"
"\n"
" Under the JavaScript runtime, exceeding the maximum (or minimum)\n"
" representable value for a floating point value will result in Infinity (or\n"
" -Infinity). Should you try to divide two infinities you will get NaN as a\n"
" result.\n"
"\n"
" When running on BEAM, exceeding the maximum (or minimum) representable\n"
" value for a floating point value will raise an error.\n"
"\n"
" ## Division by zero\n"
"\n"
" Gleam runs on the Erlang virtual machine, which does not follow the IEEE\n"
" 754 standard for floating point arithmetic and does not have an `Infinity`\n"
" value. In Erlang division by zero results in a crash, however Gleam does\n"
" not have partial functions and operators in core so instead division by zero\n"
" returns zero, a behaviour taken from Pony, Coq, and Lean.\n"
"\n"
" This may seem unexpected at first, but it is no less mathematically valid\n"
" than crashing or returning a special value. Division by zero is undefined\n"
" in mathematics.\n"
).
-file("src/gleam/float.gleam", 51).
?DOC(
" Attempts to parse a string as a `Float`, returning `Error(Nil)` if it was\n"
" not possible.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" parse(\"2.3\")\n"
" // -> Ok(2.3)\n"
" ```\n"
"\n"
" ```gleam\n"
" parse(\"ABC\")\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec parse(binary()) -> {ok, float()} | {error, nil}.
parse(String) ->
gleam_stdlib:parse_float(String).
-file("src/gleam/float.gleam", 64).
?DOC(
" Returns the string representation of the provided `Float`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_string(2.3)\n"
" // -> \"2.3\"\n"
" ```\n"
).
-spec to_string(float()) -> binary().
to_string(X) ->
gleam_stdlib:float_to_string(X).
-file("src/gleam/float.gleam", 95).
?DOC(
" Compares two `Float`s, returning an `Order`:\n"
" `Lt` for lower than, `Eq` for equals, or `Gt` for greater than.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" compare(2.0, 2.3)\n"
" // -> Lt\n"
" ```\n"
"\n"
" To handle\n"
" [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems)\n"
" you may use [`loosely_compare`](#loosely_compare) instead.\n"
).
-spec compare(float(), float()) -> gleam@order:order().
compare(A, B) ->
case A =:= B of
true ->
eq;
false ->
case A < B of
true ->
lt;
false ->
gt
end
end.
-file("src/gleam/float.gleam", 176).
?DOC(
" Compares two `Float`s, returning the smaller of the two.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" min(2.0, 2.3)\n"
" // -> 2.0\n"
" ```\n"
).
-spec min(float(), float()) -> float().
min(A, B) ->
case A < B of
true ->
A;
false ->
B
end.
-file("src/gleam/float.gleam", 192).
?DOC(
" Compares two `Float`s, returning the larger of the two.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" max(2.0, 2.3)\n"
" // -> 2.3\n"
" ```\n"
).
-spec max(float(), float()) -> float().
max(A, B) ->
case A > B of
true ->
A;
false ->
B
end.
-file("src/gleam/float.gleam", 75).
?DOC(
" Restricts a `Float` between a lower and upper bound.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" clamp(1.2, min: 1.4, max: 1.6)\n"
" // -> 1.4\n"
" ```\n"
).
-spec clamp(float(), float(), float()) -> float().
clamp(X, Min_bound, Max_bound) ->
_pipe = X,
_pipe@1 = min(_pipe, Max_bound),
max(_pipe@1, Min_bound).
-file("src/gleam/float.gleam", 210).
?DOC(
" Rounds the value to the next highest whole number as a `Float`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" ceiling(2.3)\n"
" // -> 3.0\n"
" ```\n"
).
-spec ceiling(float()) -> float().
ceiling(X) ->
math:ceil(X).
-file("src/gleam/float.gleam", 223).
?DOC(
" Rounds the value to the next lowest whole number as a `Float`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" floor(2.3)\n"
" // -> 2.0\n"
" ```\n"
).
-spec floor(float()) -> float().
floor(X) ->
math:floor(X).
-file("src/gleam/float.gleam", 261).
?DOC(
" Returns the value as an `Int`, truncating all decimal digits.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" truncate(2.4343434847383438)\n"
" // -> 2\n"
" ```\n"
).
-spec truncate(float()) -> integer().
truncate(X) ->
erlang:trunc(X).
-file("src/gleam/float.gleam", 311).
?DOC(
" Returns the absolute value of the input as a `Float`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" absolute_value(-12.5)\n"
" // -> 12.5\n"
" ```\n"
"\n"
" ```gleam\n"
" absolute_value(10.2)\n"
" // -> 10.2\n"
" ```\n"
).
-spec absolute_value(float()) -> float().
absolute_value(X) ->
case X >= +0.0 of
true ->
X;
false ->
+0.0 - X
end.
-file("src/gleam/float.gleam", 125).
?DOC(
" Compares two `Float`s within a tolerance, returning an `Order`:\n"
" `Lt` for lower than, `Eq` for equals, or `Gt` for greater than.\n"
"\n"
" This function allows Float comparison while handling\n"
" [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems).\n"
"\n"
" Notice: For `Float`s the tolerance won't be exact:\n"
" `5.3 - 5.0` is not exactly `0.3`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" loosely_compare(5.0, with: 5.3, tolerating: 0.5)\n"
" // -> Eq\n"
" ```\n"
"\n"
" If you want to check only for equality you may use\n"
" [`loosely_equals`](#loosely_equals) instead.\n"
).
-spec loosely_compare(float(), float(), float()) -> gleam@order:order().
loosely_compare(A, B, Tolerance) ->
Difference = absolute_value(A - B),
case Difference =< Tolerance of
true ->
eq;
false ->
compare(A, B)
end.
-file("src/gleam/float.gleam", 158).
?DOC(
" Checks for equality of two `Float`s within a tolerance,\n"
" returning an `Bool`.\n"
"\n"
" This function allows Float comparison while handling\n"
" [Floating Point Imprecision](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems).\n"
"\n"
" Notice: For `Float`s the tolerance won't be exact:\n"
" `5.3 - 5.0` is not exactly `0.3`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" loosely_equals(5.0, with: 5.3, tolerating: 0.5)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" loosely_equals(5.0, with: 5.1, tolerating: 0.1)\n"
" // -> False\n"
" ```\n"
).
-spec loosely_equals(float(), float(), float()) -> boolean().
loosely_equals(A, B, Tolerance) ->
Difference = absolute_value(A - B),
Difference =< Tolerance.
-file("src/gleam/float.gleam", 348).
?DOC(
" Returns the results of the base being raised to the power of the\n"
" exponent, as a `Float`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" power(2.0, -1.0)\n"
" // -> Ok(0.5)\n"
" ```\n"
"\n"
" ```gleam\n"
" power(2.0, 2.0)\n"
" // -> Ok(4.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" power(8.0, 1.5)\n"
" // -> Ok(22.627416997969522)\n"
" ```\n"
"\n"
" ```gleam\n"
" 4.0 |> power(of: 2.0)\n"
" // -> Ok(16.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" power(-1.0, 0.5)\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec power(float(), float()) -> {ok, float()} | {error, nil}.
power(Base, Exponent) ->
Fractional = (math:ceil(Exponent) - Exponent) > +0.0,
case ((Base < +0.0) andalso Fractional) orelse ((Base =:= +0.0) andalso (Exponent
< +0.0)) of
true ->
{error, nil};
false ->
{ok, math:pow(Base, Exponent)}
end.
-file("src/gleam/float.gleam", 380).
?DOC(
" Returns the square root of the input as a `Float`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" square_root(4.0)\n"
" // -> Ok(2.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" square_root(-16.0)\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec square_root(float()) -> {ok, float()} | {error, nil}.
square_root(X) ->
power(X, 0.5).
-file("src/gleam/float.gleam", 393).
?DOC(
" Returns the negative of the value provided.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" negate(1.0)\n"
" // -> -1.0\n"
" ```\n"
).
-spec negate(float()) -> float().
negate(X) ->
-1.0 * X.
-file("src/gleam/float.gleam", 240).
?DOC(
" Rounds the value to the nearest whole number as an `Int`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" round(2.3)\n"
" // -> 2\n"
" ```\n"
"\n"
" ```gleam\n"
" round(2.5)\n"
" // -> 3\n"
" ```\n"
).
-spec round(float()) -> integer().
round(X) ->
erlang:round(X).
-file("src/gleam/float.gleam", 280).
?DOC(
" Converts the value to a given precision as a `Float`.\n"
" The precision is the number of allowed decimal places.\n"
" Negative precisions are allowed and force rounding\n"
" to the nearest tenth, hundredth, thousandth etc.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_precision(2.43434348473, precision: 2)\n"
" // -> 2.43\n"
" ```\n"
"\n"
" ```gleam\n"
" to_precision(547890.453444, precision: -3)\n"
" // -> 548000.0\n"
" ```\n"
).
-spec to_precision(float(), integer()) -> float().
to_precision(X, Precision) ->
case Precision =< 0 of
true ->
Factor = math:pow(10.0, erlang:float(- Precision)),
erlang:float(erlang:round(case Factor of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> X / Gleam@denominator
end)) * Factor;
false ->
Factor@1 = math:pow(10.0, erlang:float(Precision)),
case Factor@1 of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@1 -> erlang:float(erlang:round(X * Factor@1))
/ Gleam@denominator@1
end
end.
-file("src/gleam/float.gleam", 410).
-spec sum_loop(list(float()), float()) -> float().
sum_loop(Numbers, Initial) ->
case Numbers of
[First | Rest] ->
sum_loop(Rest, First + Initial);
[] ->
Initial
end.
-file("src/gleam/float.gleam", 406).
?DOC(
" Sums a list of `Float`s.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" sum([1.0, 2.2, 3.3])\n"
" // -> 6.5\n"
" ```\n"
).
-spec sum(list(float())) -> float().
sum(Numbers) ->
sum_loop(Numbers, +0.0).
-file("src/gleam/float.gleam", 430).
-spec product_loop(list(float()), float()) -> float().
product_loop(Numbers, Initial) ->
case Numbers of
[First | Rest] ->
product_loop(Rest, First * Initial);
[] ->
Initial
end.
-file("src/gleam/float.gleam", 426).
?DOC(
" Multiplies a list of `Float`s and returns the product.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" product([2.5, 3.2, 4.2])\n"
" // -> 33.6\n"
" ```\n"
).
-spec product(list(float())) -> float().
product(Numbers) ->
product_loop(Numbers, 1.0).
-file("src/gleam/float.gleam", 452).
?DOC(
" Generates a random float between the given zero (inclusive) and one\n"
" (exclusive).\n"
"\n"
" On Erlang this updates the random state in the process dictionary.\n"
" See: <https://www.erlang.org/doc/man/rand.html#uniform-0>\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" random()\n"
" // -> 0.646355926896028\n"
" ```\n"
).
-spec random() -> float().
random() ->
rand:uniform().
-file("src/gleam/float.gleam", 481).
?DOC(
" Computes the modulo of an float division of inputs as a `Result`.\n"
"\n"
" Returns division of the inputs as a `Result`: If the given divisor equals\n"
" `0`, this function returns an `Error`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" modulo(13.3, by: 3.3)\n"
" // -> Ok(0.1)\n"
" ```\n"
"\n"
" ```gleam\n"
" modulo(-13.3, by: 3.3)\n"
" // -> Ok(3.2)\n"
" ```\n"
"\n"
" ```gleam\n"
" modulo(13.3, by: -3.3)\n"
" // -> Ok(-3.2)\n"
" ```\n"
"\n"
" ```gleam\n"
" modulo(-13.3, by: -3.3)\n"
" // -> Ok(-0.1)\n"
" ```\n"
).
-spec modulo(float(), float()) -> {ok, float()} | {error, nil}.
modulo(Dividend, Divisor) ->
case Divisor of
+0.0 ->
{error, nil};
_ ->
{ok, Dividend - (math:floor(case Divisor of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> Dividend / Gleam@denominator
end) * Divisor)}
end.
-file("src/gleam/float.gleam", 502).
?DOC(
" Returns division of the inputs as a `Result`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" divide(0.0, 1.0)\n"
" // -> Ok(0.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" divide(1.0, 0.0)\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec divide(float(), float()) -> {ok, float()} | {error, nil}.
divide(A, B) ->
case B of
+0.0 ->
{error, nil};
B@1 ->
{ok, case B@1 of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> A / Gleam@denominator
end}
end.
-file("src/gleam/float.gleam", 533).
?DOC(
" Adds two floats together.\n"
"\n"
" It's the function equivalent of the `+.` operator.\n"
" This function is useful in higher order functions or pipes.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" add(1.0, 2.0)\n"
" // -> 3.0\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/list\n"
"\n"
" list.fold([1.0, 2.0, 3.0], 0.0, add)\n"
" // -> 6.0\n"
" ```\n"
"\n"
" ```gleam\n"
" 3.0 |> add(2.0)\n"
" // -> 5.0\n"
" ```\n"
).
-spec add(float(), float()) -> float().
add(A, B) ->
A + B.
-file("src/gleam/float.gleam", 561).
?DOC(
" Multiplies two floats together.\n"
"\n"
" It's the function equivalent of the `*.` operator.\n"
" This function is useful in higher order functions or pipes.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" multiply(2.0, 4.0)\n"
" // -> 8.0\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/list\n"
"\n"
" list.fold([2.0, 3.0, 4.0], 1.0, multiply)\n"
" // -> 24.0\n"
" ```\n"
"\n"
" ```gleam\n"
" 3.0 |> multiply(2.0)\n"
" // -> 6.0\n"
" ```\n"
).
-spec multiply(float(), float()) -> float().
multiply(A, B) ->
A * B.
-file("src/gleam/float.gleam", 594).
?DOC(
" Subtracts one float from another.\n"
"\n"
" It's the function equivalent of the `-.` operator.\n"
" This function is useful in higher order functions or pipes.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" subtract(3.0, 1.0)\n"
" // -> 2.0\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/list\n"
"\n"
" list.fold([1.0, 2.0, 3.0], 10.0, subtract)\n"
" // -> 4.0\n"
" ```\n"
"\n"
" ```gleam\n"
" 3.0 |> subtract(_, 2.0)\n"
" // -> 1.0\n"
" ```\n"
"\n"
" ```gleam\n"
" 3.0 |> subtract(2.0, _)\n"
" // -> -1.0\n"
" ```\n"
).
-spec subtract(float(), float()) -> float().
subtract(A, B) ->
A - B.
-file("src/gleam/float.gleam", 623).
?DOC(
" Returns the natural logarithm (base e) of the given as a `Result`. If the\n"
" input is less than or equal to 0, returns `Error(Nil)`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" logarithm(1.0)\n"
" // -> Ok(0.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" logarithm(2.718281828459045) // e\n"
" // -> Ok(1.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" logarithm(0.0)\n"
" // -> Error(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" logarithm(-1.0)\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec logarithm(float()) -> {ok, float()} | {error, nil}.
logarithm(X) ->
case X =< +0.0 of
true ->
{error, nil};
false ->
{ok, math:log(X)}
end.
-file("src/gleam/float.gleam", 661).
?DOC(
" Returns e (Euler's number) raised to the power of the given exponent, as\n"
" a `Float`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" exponential(0.0)\n"
" // -> Ok(1.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" exponential(1.0)\n"
" // -> Ok(2.718281828459045)\n"
" ```\n"
"\n"
" ```gleam\n"
" exponential(-1.0)\n"
" // -> Ok(0.36787944117144233)\n"
" ```\n"
).
-spec exponential(float()) -> float().
exponential(X) ->
math:exp(X).

View file

@ -0,0 +1,30 @@
-module(gleam@function).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/gleam/function.gleam").
-export([identity/1, tap/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-file("src/gleam/function.gleam", 3).
?DOC(" Takes a single argument and always returns its input value.\n").
-spec identity(CNY) -> CNY.
identity(X) ->
X.
-file("src/gleam/function.gleam", 12).
?DOC(
" Takes an argument and a single function, calls that function with that\n"
" argument and returns that argument instead of the function return value.\n"
"\n"
" Useful for running synchronous side effects in a pipeline.\n"
).
-spec tap(CNZ, fun((CNZ) -> any())) -> CNZ.
tap(Arg, Effect) ->
Effect(Arg),
Arg.

View file

@ -0,0 +1,984 @@
-module(gleam@int).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/gleam/int.gleam").
-export([absolute_value/1, parse/1, base_parse/2, to_string/1, to_base_string/2, to_base2/1, to_base8/1, to_base16/1, to_base36/1, to_float/1, power/2, square_root/1, compare/2, min/2, max/2, clamp/3, is_even/1, is_odd/1, negate/1, sum/1, product/1, digits/2, undigits/2, random/1, divide/2, remainder/2, modulo/2, floor_divide/2, add/2, multiply/2, subtract/2, bitwise_and/2, bitwise_not/1, bitwise_or/2, bitwise_exclusive_or/2, bitwise_shift_left/2, bitwise_shift_right/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Functions for working with integers.\n"
"\n"
" ## Division by zero\n"
"\n"
" In Erlang division by zero results in a crash, however Gleam does not have\n"
" partial functions and operators in core so instead division by zero returns\n"
" zero, a behaviour taken from Pony, Coq, and Lean.\n"
"\n"
" This may seem unexpected at first, but it is no less mathematically valid\n"
" than crashing or returning a special value. Division by zero is undefined\n"
" in mathematics.\n"
).
-file("src/gleam/int.gleam", 30).
?DOC(
" Returns the absolute value of the input.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" absolute_value(-12)\n"
" // -> 12\n"
" ```\n"
"\n"
" ```gleam\n"
" absolute_value(10)\n"
" // -> 10\n"
" ```\n"
).
-spec absolute_value(integer()) -> integer().
absolute_value(X) ->
case X >= 0 of
true ->
X;
false ->
X * -1
end.
-file("src/gleam/int.gleam", 107).
?DOC(
" Parses a given string as an int if possible.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" parse(\"2\")\n"
" // -> Ok(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" parse(\"ABC\")\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec parse(binary()) -> {ok, integer()} | {error, nil}.
parse(String) ->
gleam_stdlib:parse_int(String).
-file("src/gleam/int.gleam", 139).
?DOC(
" Parses a given string as an int in a given base if possible.\n"
" Supports only bases 2 to 36, for values outside of which this function returns an `Error(Nil)`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" base_parse(\"10\", 2)\n"
" // -> Ok(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" base_parse(\"30\", 16)\n"
" // -> Ok(48)\n"
" ```\n"
"\n"
" ```gleam\n"
" base_parse(\"1C\", 36)\n"
" // -> Ok(48)\n"
" ```\n"
"\n"
" ```gleam\n"
" base_parse(\"48\", 1)\n"
" // -> Error(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" base_parse(\"48\", 37)\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec base_parse(binary(), integer()) -> {ok, integer()} | {error, nil}.
base_parse(String, Base) ->
case (Base >= 2) andalso (Base =< 36) of
true ->
gleam_stdlib:int_from_base_string(String, Base);
false ->
{error, nil}
end.
-file("src/gleam/int.gleam", 161).
?DOC(
" Prints a given int to a string.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_string(2)\n"
" // -> \"2\"\n"
" ```\n"
).
-spec to_string(integer()) -> binary().
to_string(X) ->
erlang:integer_to_binary(X).
-file("src/gleam/int.gleam", 194).
?DOC(
" Prints a given int to a string using the base number provided.\n"
" Supports only bases 2 to 36, for values outside of which this function returns an `Error(Nil)`.\n"
" For common bases (2, 8, 16, 36), use the `to_baseN` functions.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_base_string(2, 2)\n"
" // -> Ok(\"10\")\n"
" ```\n"
"\n"
" ```gleam\n"
" to_base_string(48, 16)\n"
" // -> Ok(\"30\")\n"
" ```\n"
"\n"
" ```gleam\n"
" to_base_string(48, 36)\n"
" // -> Ok(\"1C\")\n"
" ```\n"
"\n"
" ```gleam\n"
" to_base_string(48, 1)\n"
" // -> Error(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" to_base_string(48, 37)\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec to_base_string(integer(), integer()) -> {ok, binary()} | {error, nil}.
to_base_string(X, Base) ->
case (Base >= 2) andalso (Base =< 36) of
true ->
{ok, erlang:integer_to_binary(X, Base)};
false ->
{error, nil}
end.
-file("src/gleam/int.gleam", 214).
?DOC(
" Prints a given int to a string using base-2.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_base2(2)\n"
" // -> \"10\"\n"
" ```\n"
).
-spec to_base2(integer()) -> binary().
to_base2(X) ->
erlang:integer_to_binary(X, 2).
-file("src/gleam/int.gleam", 227).
?DOC(
" Prints a given int to a string using base-8.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_base8(15)\n"
" // -> \"17\"\n"
" ```\n"
).
-spec to_base8(integer()) -> binary().
to_base8(X) ->
erlang:integer_to_binary(X, 8).
-file("src/gleam/int.gleam", 240).
?DOC(
" Prints a given int to a string using base-16.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_base16(48)\n"
" // -> \"30\"\n"
" ```\n"
).
-spec to_base16(integer()) -> binary().
to_base16(X) ->
erlang:integer_to_binary(X, 16).
-file("src/gleam/int.gleam", 253).
?DOC(
" Prints a given int to a string using base-36.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_base36(48)\n"
" // -> \"1C\"\n"
" ```\n"
).
-spec to_base36(integer()) -> binary().
to_base36(X) ->
erlang:integer_to_binary(X, 36).
-file("src/gleam/int.gleam", 278).
?DOC(
" Takes an int and returns its value as a float.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_float(5)\n"
" // -> 5.0\n"
" ```\n"
"\n"
" ```gleam\n"
" to_float(0)\n"
" // -> 0.0\n"
" ```\n"
"\n"
" ```gleam\n"
" to_float(-3)\n"
" // -> -3.0\n"
" ```\n"
).
-spec to_float(integer()) -> float().
to_float(X) ->
erlang:float(X).
-file("src/gleam/int.gleam", 67).
?DOC(
" Returns the results of the base being raised to the power of the\n"
" exponent, as a `Float`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" power(2, -1.0)\n"
" // -> Ok(0.5)\n"
" ```\n"
"\n"
" ```gleam\n"
" power(2, 2.0)\n"
" // -> Ok(4.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" power(8, 1.5)\n"
" // -> Ok(22.627416997969522)\n"
" ```\n"
"\n"
" ```gleam\n"
" 4 |> power(of: 2.0)\n"
" // -> Ok(16.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" power(-1, 0.5)\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec power(integer(), float()) -> {ok, float()} | {error, nil}.
power(Base, Exponent) ->
_pipe = erlang:float(Base),
gleam@float:power(_pipe, Exponent).
-file("src/gleam/int.gleam", 86).
?DOC(
" Returns the square root of the input as a `Float`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" square_root(4)\n"
" // -> Ok(2.0)\n"
" ```\n"
"\n"
" ```gleam\n"
" square_root(-16)\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec square_root(integer()) -> {ok, float()} | {error, nil}.
square_root(X) ->
_pipe = erlang:float(X),
gleam@float:square_root(_pipe).
-file("src/gleam/int.gleam", 314).
?DOC(
" Compares two ints, returning an order.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" compare(2, 3)\n"
" // -> Lt\n"
" ```\n"
"\n"
" ```gleam\n"
" compare(4, 3)\n"
" // -> Gt\n"
" ```\n"
"\n"
" ```gleam\n"
" compare(3, 3)\n"
" // -> Eq\n"
" ```\n"
).
-spec compare(integer(), integer()) -> gleam@order:order().
compare(A, B) ->
case A =:= B of
true ->
eq;
false ->
case A < B of
true ->
lt;
false ->
gt
end
end.
-file("src/gleam/int.gleam", 334).
?DOC(
" Compares two ints, returning the smaller of the two.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" min(2, 3)\n"
" // -> 2\n"
" ```\n"
).
-spec min(integer(), integer()) -> integer().
min(A, B) ->
case A < B of
true ->
A;
false ->
B
end.
-file("src/gleam/int.gleam", 350).
?DOC(
" Compares two ints, returning the larger of the two.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" max(2, 3)\n"
" // -> 3\n"
" ```\n"
).
-spec max(integer(), integer()) -> integer().
max(A, B) ->
case A > B of
true ->
A;
false ->
B
end.
-file("src/gleam/int.gleam", 289).
?DOC(
" Restricts an int between a lower and upper bound.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" clamp(40, min: 50, max: 60)\n"
" // -> 50\n"
" ```\n"
).
-spec clamp(integer(), integer(), integer()) -> integer().
clamp(X, Min_bound, Max_bound) ->
_pipe = X,
_pipe@1 = min(_pipe, Max_bound),
max(_pipe@1, Min_bound).
-file("src/gleam/int.gleam", 371).
?DOC(
" Returns whether the value provided is even.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" is_even(2)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" is_even(3)\n"
" // -> False\n"
" ```\n"
).
-spec is_even(integer()) -> boolean().
is_even(X) ->
(X rem 2) =:= 0.
-file("src/gleam/int.gleam", 389).
?DOC(
" Returns whether the value provided is odd.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" is_odd(3)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" is_odd(2)\n"
" // -> False\n"
" ```\n"
).
-spec is_odd(integer()) -> boolean().
is_odd(X) ->
(X rem 2) /= 0.
-file("src/gleam/int.gleam", 402).
?DOC(
" Returns the negative of the value provided.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" negate(1)\n"
" // -> -1\n"
" ```\n"
).
-spec negate(integer()) -> integer().
negate(X) ->
-1 * X.
-file("src/gleam/int.gleam", 419).
-spec sum_loop(list(integer()), integer()) -> integer().
sum_loop(Numbers, Initial) ->
case Numbers of
[First | Rest] ->
sum_loop(Rest, First + Initial);
[] ->
Initial
end.
-file("src/gleam/int.gleam", 415).
?DOC(
" Sums a list of ints.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" sum([1, 2, 3])\n"
" // -> 6\n"
" ```\n"
).
-spec sum(list(integer())) -> integer().
sum(Numbers) ->
sum_loop(Numbers, 0).
-file("src/gleam/int.gleam", 439).
-spec product_loop(list(integer()), integer()) -> integer().
product_loop(Numbers, Initial) ->
case Numbers of
[First | Rest] ->
product_loop(Rest, First * Initial);
[] ->
Initial
end.
-file("src/gleam/int.gleam", 435).
?DOC(
" Multiplies a list of ints and returns the product.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" product([2, 3, 4])\n"
" // -> 24\n"
" ```\n"
).
-spec product(list(integer())) -> integer().
product(Numbers) ->
product_loop(Numbers, 1).
-file("src/gleam/int.gleam", 454).
-spec digits_loop(integer(), integer(), list(integer())) -> list(integer()).
digits_loop(X, Base, Acc) ->
case absolute_value(X) < Base of
true ->
[X | Acc];
false ->
digits_loop(case Base of
0 -> 0;
Gleam@denominator -> X div Gleam@denominator
end, Base, [case Base of
0 -> 0;
Gleam@denominator@1 -> X rem Gleam@denominator@1
end | Acc])
end.
-file("src/gleam/int.gleam", 447).
-spec digits(integer(), integer()) -> {ok, list(integer())} | {error, nil}.
digits(X, Base) ->
case Base < 2 of
true ->
{error, nil};
false ->
{ok, digits_loop(X, Base, [])}
end.
-file("src/gleam/int.gleam", 469).
-spec undigits_loop(list(integer()), integer(), integer()) -> {ok, integer()} |
{error, nil}.
undigits_loop(Numbers, Base, Acc) ->
case Numbers of
[] ->
{ok, Acc};
[Digit | _] when Digit >= Base ->
{error, nil};
[Digit@1 | Rest] ->
undigits_loop(Rest, Base, (Acc * Base) + Digit@1)
end.
-file("src/gleam/int.gleam", 462).
-spec undigits(list(integer()), integer()) -> {ok, integer()} | {error, nil}.
undigits(Numbers, Base) ->
case Base < 2 of
true ->
{error, nil};
false ->
undigits_loop(Numbers, Base, 0)
end.
-file("src/gleam/int.gleam", 498).
?DOC(
" Generates a random int between zero and the given maximum.\n"
"\n"
" The lower number is inclusive, the upper number is exclusive.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" random(10)\n"
" // -> 4\n"
" ```\n"
"\n"
" ```gleam\n"
" random(1)\n"
" // -> 0\n"
" ```\n"
"\n"
" ```gleam\n"
" random(-1)\n"
" // -> -1\n"
" ```\n"
).
-spec random(integer()) -> integer().
random(Max) ->
_pipe = (rand:uniform() * erlang:float(Max)),
_pipe@1 = math:floor(_pipe),
erlang:round(_pipe@1).
-file("src/gleam/int.gleam", 531).
?DOC(
" Performs a truncated integer division.\n"
"\n"
" Returns division of the inputs as a `Result`: If the given divisor equals\n"
" `0`, this function returns an `Error`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" divide(0, 1)\n"
" // -> Ok(0)\n"
" ```\n"
"\n"
" ```gleam\n"
" divide(1, 0)\n"
" // -> Error(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" divide(5, 2)\n"
" // -> Ok(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" divide(-99, 2)\n"
" // -> Ok(-49)\n"
" ```\n"
).
-spec divide(integer(), integer()) -> {ok, integer()} | {error, nil}.
divide(Dividend, Divisor) ->
case Divisor of
0 ->
{error, nil};
Divisor@1 ->
{ok, case Divisor@1 of
0 -> 0;
Gleam@denominator -> Dividend div Gleam@denominator
end}
end.
-file("src/gleam/int.gleam", 583).
?DOC(
" Computes the remainder of an integer division of inputs as a `Result`.\n"
"\n"
" Returns division of the inputs as a `Result`: If the given divisor equals\n"
" `0`, this function returns an `Error`.\n"
"\n"
" Most the time you will want to use the `%` operator instead of this\n"
" function.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" remainder(3, 2)\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" remainder(1, 0)\n"
" // -> Error(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" remainder(10, -1)\n"
" // -> Ok(0)\n"
" ```\n"
"\n"
" ```gleam\n"
" remainder(13, by: 3)\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" remainder(-13, by: 3)\n"
" // -> Ok(-1)\n"
" ```\n"
"\n"
" ```gleam\n"
" remainder(13, by: -3)\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" remainder(-13, by: -3)\n"
" // -> Ok(-1)\n"
" ```\n"
).
-spec remainder(integer(), integer()) -> {ok, integer()} | {error, nil}.
remainder(Dividend, Divisor) ->
case Divisor of
0 ->
{error, nil};
Divisor@1 ->
{ok, case Divisor@1 of
0 -> 0;
Gleam@denominator -> Dividend rem Gleam@denominator
end}
end.
-file("src/gleam/int.gleam", 625).
?DOC(
" Computes the modulo of an integer division of inputs as a `Result`.\n"
"\n"
" Returns division of the inputs as a `Result`: If the given divisor equals\n"
" `0`, this function returns an `Error`.\n"
"\n"
" Most the time you will want to use the `%` operator instead of this\n"
" function.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" modulo(3, 2)\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" modulo(1, 0)\n"
" // -> Error(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" modulo(10, -1)\n"
" // -> Ok(0)\n"
" ```\n"
"\n"
" ```gleam\n"
" modulo(13, by: 3)\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" modulo(-13, by: 3)\n"
" // -> Ok(2)\n"
" ```\n"
).
-spec modulo(integer(), integer()) -> {ok, integer()} | {error, nil}.
modulo(Dividend, Divisor) ->
case Divisor of
0 ->
{error, nil};
_ ->
Remainder = case Divisor of
0 -> 0;
Gleam@denominator -> Dividend rem Gleam@denominator
end,
case (Remainder * Divisor) < 0 of
true ->
{ok, Remainder + Divisor};
false ->
{ok, Remainder}
end
end.
-file("src/gleam/int.gleam", 669).
?DOC(
" Performs a *floored* integer division, which means that the result will\n"
" always be rounded towards negative infinity.\n"
"\n"
" If you want to perform truncated integer division (rounding towards zero),\n"
" use `int.divide()` or the `/` operator instead.\n"
"\n"
" Returns division of the inputs as a `Result`: If the given divisor equals\n"
" `0`, this function returns an `Error`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" floor_divide(1, 0)\n"
" // -> Error(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" floor_divide(5, 2)\n"
" // -> Ok(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" floor_divide(6, -4)\n"
" // -> Ok(-2)\n"
" ```\n"
"\n"
" ```gleam\n"
" floor_divide(-99, 2)\n"
" // -> Ok(-50)\n"
" ```\n"
).
-spec floor_divide(integer(), integer()) -> {ok, integer()} | {error, nil}.
floor_divide(Dividend, Divisor) ->
case Divisor of
0 ->
{error, nil};
Divisor@1 ->
case ((Dividend * Divisor@1) < 0) andalso ((case Divisor@1 of
0 -> 0;
Gleam@denominator -> Dividend rem Gleam@denominator
end) /= 0) of
true ->
{ok, (case Divisor@1 of
0 -> 0;
Gleam@denominator@1 -> Dividend div Gleam@denominator@1
end) - 1};
false ->
{ok, case Divisor@1 of
0 -> 0;
Gleam@denominator@2 -> Dividend div Gleam@denominator@2
end}
end
end.
-file("src/gleam/int.gleam", 703).
?DOC(
" Adds two integers together.\n"
"\n"
" It's the function equivalent of the `+` operator.\n"
" This function is useful in higher order functions or pipes.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" add(1, 2)\n"
" // -> 3\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/list\n"
" list.fold([1, 2, 3], 0, add)\n"
" // -> 6\n"
" ```\n"
"\n"
" ```gleam\n"
" 3 |> add(2)\n"
" // -> 5\n"
" ```\n"
).
-spec add(integer(), integer()) -> integer().
add(A, B) ->
A + B.
-file("src/gleam/int.gleam", 731).
?DOC(
" Multiplies two integers together.\n"
"\n"
" It's the function equivalent of the `*` operator.\n"
" This function is useful in higher order functions or pipes.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" multiply(2, 4)\n"
" // -> 8\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/list\n"
"\n"
" list.fold([2, 3, 4], 1, multiply)\n"
" // -> 24\n"
" ```\n"
"\n"
" ```gleam\n"
" 3 |> multiply(2)\n"
" // -> 6\n"
" ```\n"
).
-spec multiply(integer(), integer()) -> integer().
multiply(A, B) ->
A * B.
-file("src/gleam/int.gleam", 764).
?DOC(
" Subtracts one int from another.\n"
"\n"
" It's the function equivalent of the `-` operator.\n"
" This function is useful in higher order functions or pipes.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" subtract(3, 1)\n"
" // -> 2\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/list\n"
"\n"
" list.fold([1, 2, 3], 10, subtract)\n"
" // -> 4\n"
" ```\n"
"\n"
" ```gleam\n"
" 3 |> subtract(2)\n"
" // -> 1\n"
" ```\n"
"\n"
" ```gleam\n"
" 3 |> subtract(2, _)\n"
" // -> -1\n"
" ```\n"
).
-spec subtract(integer(), integer()) -> integer().
subtract(A, B) ->
A - B.
-file("src/gleam/int.gleam", 776).
?DOC(
" Calculates the bitwise AND of its arguments.\n"
"\n"
" The exact behaviour of this function depends on the target platform.\n"
" On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n"
" is equivalent to bitwise operations on big-ints.\n"
).
-spec bitwise_and(integer(), integer()) -> integer().
bitwise_and(X, Y) ->
erlang:'band'(X, Y).
-file("src/gleam/int.gleam", 786).
?DOC(
" Calculates the bitwise NOT of its argument.\n"
"\n"
" The exact behaviour of this function depends on the target platform.\n"
" On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n"
" is equivalent to bitwise operations on big-ints.\n"
).
-spec bitwise_not(integer()) -> integer().
bitwise_not(X) ->
erlang:'bnot'(X).
-file("src/gleam/int.gleam", 796).
?DOC(
" Calculates the bitwise OR of its arguments.\n"
"\n"
" The exact behaviour of this function depends on the target platform.\n"
" On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n"
" is equivalent to bitwise operations on big-ints.\n"
).
-spec bitwise_or(integer(), integer()) -> integer().
bitwise_or(X, Y) ->
erlang:'bor'(X, Y).
-file("src/gleam/int.gleam", 806).
?DOC(
" Calculates the bitwise XOR of its arguments.\n"
"\n"
" The exact behaviour of this function depends on the target platform.\n"
" On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n"
" is equivalent to bitwise operations on big-ints.\n"
).
-spec bitwise_exclusive_or(integer(), integer()) -> integer().
bitwise_exclusive_or(X, Y) ->
erlang:'bxor'(X, Y).
-file("src/gleam/int.gleam", 816).
?DOC(
" Calculates the result of an arithmetic left bitshift.\n"
"\n"
" The exact behaviour of this function depends on the target platform.\n"
" On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n"
" is equivalent to bitwise operations on big-ints.\n"
).
-spec bitwise_shift_left(integer(), integer()) -> integer().
bitwise_shift_left(X, Y) ->
erlang:'bsl'(X, Y).
-file("src/gleam/int.gleam", 826).
?DOC(
" Calculates the result of an arithmetic right bitshift.\n"
"\n"
" The exact behaviour of this function depends on the target platform.\n"
" On Erlang it is equivalent to bitwise operations on ints, on JavaScript it\n"
" is equivalent to bitwise operations on big-ints.\n"
).
-spec bitwise_shift_right(integer(), integer()) -> integer().
bitwise_shift_right(X, Y) ->
erlang:'bsr'(X, Y).

View file

@ -0,0 +1,80 @@
-module(gleam@io).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/gleam/io.gleam").
-export([print/1, print_error/1, println/1, println_error/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-file("src/gleam/io.gleam", 15).
?DOC(
" Writes a string to standard output (stdout).\n"
"\n"
" If you want your output to be printed on its own line see `println`.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" io.print(\"Hi mum\")\n"
" // -> Nil\n"
" // Hi mum\n"
" ```\n"
).
-spec print(binary()) -> nil.
print(String) ->
gleam_stdlib:print(String).
-file("src/gleam/io.gleam", 31).
?DOC(
" Writes a string to standard error (stderr).\n"
"\n"
" If you want your output to be printed on its own line see `println_error`.\n"
"\n"
" ## Example\n"
"\n"
" ```\n"
" io.print_error(\"Hi pop\")\n"
" // -> Nil\n"
" // Hi pop\n"
" ```\n"
).
-spec print_error(binary()) -> nil.
print_error(String) ->
gleam_stdlib:print_error(String).
-file("src/gleam/io.gleam", 45).
?DOC(
" Writes a string to standard output (stdout), appending a newline to the end.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" io.println(\"Hi mum\")\n"
" // -> Nil\n"
" // Hi mum\n"
" ```\n"
).
-spec println(binary()) -> nil.
println(String) ->
gleam_stdlib:println(String).
-file("src/gleam/io.gleam", 59).
?DOC(
" Writes a string to standard error (stderr), appending a newline to the end.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" io.println_error(\"Hi pop\")\n"
" // -> Nil\n"
" // Hi pop\n"
" ```\n"
).
-spec println_error(binary()) -> nil.
println_error(String) ->
gleam_stdlib:println_error(String).

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,413 @@
-module(gleam@option).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/gleam/option.gleam").
-export([all/1, is_some/1, is_none/1, to_result/2, from_result/1, unwrap/2, lazy_unwrap/2, map/2, flatten/1, then/2, 'or'/2, lazy_or/2, values/1]).
-export_type([option/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type option(GA) :: {some, GA} | none.
-file("src/gleam/option.gleam", 59).
-spec reverse_and_prepend(list(GP), list(GP)) -> list(GP).
reverse_and_prepend(Prefix, Suffix) ->
case Prefix of
[] ->
Suffix;
[First | Rest] ->
reverse_and_prepend(Rest, [First | Suffix])
end.
-file("src/gleam/option.gleam", 44).
-spec all_loop(list(option(GG)), list(GG)) -> option(list(GG)).
all_loop(List, Acc) ->
case List of
[] ->
{some, lists:reverse(Acc)};
[none | _] ->
none;
[{some, First} | Rest] ->
all_loop(Rest, [First | Acc])
end.
-file("src/gleam/option.gleam", 40).
?DOC(
" Combines a list of `Option`s into a single `Option`.\n"
" If all elements in the list are `Some` then returns a `Some` holding the list of values.\n"
" If any element is `None` then returns`None`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" all([Some(1), Some(2)])\n"
" // -> Some([1, 2])\n"
" ```\n"
"\n"
" ```gleam\n"
" all([Some(1), None])\n"
" // -> None\n"
" ```\n"
).
-spec all(list(option(GB))) -> option(list(GB)).
all(List) ->
all_loop(List, []).
-file("src/gleam/option.gleam", 80).
?DOC(
" Checks whether the `Option` is a `Some` value.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" is_some(Some(1))\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" is_some(None)\n"
" // -> False\n"
" ```\n"
).
-spec is_some(option(any())) -> boolean().
is_some(Option) ->
Option /= none.
-file("src/gleam/option.gleam", 98).
?DOC(
" Checks whether the `Option` is a `None` value.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" is_none(Some(1))\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" is_none(None)\n"
" // -> True\n"
" ```\n"
).
-spec is_none(option(any())) -> boolean().
is_none(Option) ->
Option =:= none.
-file("src/gleam/option.gleam", 116).
?DOC(
" Converts an `Option` type to a `Result` type.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_result(Some(1), \"some_error\")\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" to_result(None, \"some_error\")\n"
" // -> Error(\"some_error\")\n"
" ```\n"
).
-spec to_result(option(GX), HA) -> {ok, GX} | {error, HA}.
to_result(Option, E) ->
case Option of
{some, A} ->
{ok, A};
none ->
{error, E}
end.
-file("src/gleam/option.gleam", 137).
?DOC(
" Converts a `Result` type to an `Option` type.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_result(Ok(1))\n"
" // -> Some(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" from_result(Error(\"some_error\"))\n"
" // -> None\n"
" ```\n"
).
-spec from_result({ok, HD} | {error, any()}) -> option(HD).
from_result(Result) ->
case Result of
{ok, A} ->
{some, A};
{error, _} ->
none
end.
-file("src/gleam/option.gleam", 158).
?DOC(
" Extracts the value from an `Option`, returning a default value if there is none.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" unwrap(Some(1), 0)\n"
" // -> 1\n"
" ```\n"
"\n"
" ```gleam\n"
" unwrap(None, 0)\n"
" // -> 0\n"
" ```\n"
).
-spec unwrap(option(HI), HI) -> HI.
unwrap(Option, Default) ->
case Option of
{some, X} ->
X;
none ->
Default
end.
-file("src/gleam/option.gleam", 179).
?DOC(
" Extracts the value from an `Option`, evaluating the default function if the option is `None`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" lazy_unwrap(Some(1), fn() { 0 })\n"
" // -> 1\n"
" ```\n"
"\n"
" ```gleam\n"
" lazy_unwrap(None, fn() { 0 })\n"
" // -> 0\n"
" ```\n"
).
-spec lazy_unwrap(option(HK), fun(() -> HK)) -> HK.
lazy_unwrap(Option, Default) ->
case Option of
{some, X} ->
X;
none ->
Default()
end.
-file("src/gleam/option.gleam", 204).
?DOC(
" Updates a value held within the `Some` of an `Option` by calling a given function\n"
" on it.\n"
"\n"
" If the `Option` is a `None` rather than `Some`, the function is not called and the\n"
" `Option` stays the same.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" map(over: Some(1), with: fn(x) { x + 1 })\n"
" // -> Some(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" map(over: None, with: fn(x) { x + 1 })\n"
" // -> None\n"
" ```\n"
).
-spec map(option(HM), fun((HM) -> HO)) -> option(HO).
map(Option, Fun) ->
case Option of
{some, X} ->
{some, Fun(X)};
none ->
none
end.
-file("src/gleam/option.gleam", 230).
?DOC(
" Merges a nested `Option` into a single layer.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" flatten(Some(Some(1)))\n"
" // -> Some(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" flatten(Some(None))\n"
" // -> None\n"
" ```\n"
"\n"
" ```gleam\n"
" flatten(None)\n"
" // -> None\n"
" ```\n"
).
-spec flatten(option(option(HQ))) -> option(HQ).
flatten(Option) ->
case Option of
{some, X} ->
X;
none ->
none
end.
-file("src/gleam/option.gleam", 269).
?DOC(
" Updates a value held within the `Some` of an `Option` by calling a given function\n"
" on it, where the given function also returns an `Option`. The two options are\n"
" then merged together into one `Option`.\n"
"\n"
" If the `Option` is a `None` rather than `Some` the function is not called and the\n"
" option stays the same.\n"
"\n"
" This function is the equivalent of calling `map` followed by `flatten`, and\n"
" it is useful for chaining together multiple functions that return `Option`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" then(Some(1), fn(x) { Some(x + 1) })\n"
" // -> Some(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" then(Some(1), fn(x) { Some(#(\"a\", x)) })\n"
" // -> Some(#(\"a\", 1))\n"
" ```\n"
"\n"
" ```gleam\n"
" then(Some(1), fn(_) { None })\n"
" // -> None\n"
" ```\n"
"\n"
" ```gleam\n"
" then(None, fn(x) { Some(x + 1) })\n"
" // -> None\n"
" ```\n"
).
-spec then(option(HU), fun((HU) -> option(HW))) -> option(HW).
then(Option, Fun) ->
case Option of
{some, X} ->
Fun(X);
none ->
none
end.
-file("src/gleam/option.gleam", 300).
?DOC(
" Returns the first value if it is `Some`, otherwise returns the second value.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" or(Some(1), Some(2))\n"
" // -> Some(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" or(Some(1), None)\n"
" // -> Some(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" or(None, Some(2))\n"
" // -> Some(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" or(None, None)\n"
" // -> None\n"
" ```\n"
).
-spec 'or'(option(HZ), option(HZ)) -> option(HZ).
'or'(First, Second) ->
case First of
{some, _} ->
First;
none ->
Second
end.
-file("src/gleam/option.gleam", 331).
?DOC(
" Returns the first value if it is `Some`, otherwise evaluates the given function for a fallback value.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" lazy_or(Some(1), fn() { Some(2) })\n"
" // -> Some(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" lazy_or(Some(1), fn() { None })\n"
" // -> Some(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" lazy_or(None, fn() { Some(2) })\n"
" // -> Some(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" lazy_or(None, fn() { None })\n"
" // -> None\n"
" ```\n"
).
-spec lazy_or(option(ID), fun(() -> option(ID))) -> option(ID).
lazy_or(First, Second) ->
case First of
{some, _} ->
First;
none ->
Second()
end.
-file("src/gleam/option.gleam", 352).
-spec values_loop(list(option(IL)), list(IL)) -> list(IL).
values_loop(List, Acc) ->
case List of
[] ->
lists:reverse(Acc);
[none | Rest] ->
values_loop(Rest, Acc);
[{some, First} | Rest@1] ->
values_loop(Rest@1, [First | Acc])
end.
-file("src/gleam/option.gleam", 348).
?DOC(
" Given a list of `Option`s,\n"
" returns only the values inside `Some`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" values([Some(1), None, Some(3)])\n"
" // -> [1, 3]\n"
" ```\n"
).
-spec values(list(option(IH))) -> list(IH).
values(Options) ->
values_loop(Options, []).

View file

@ -0,0 +1,200 @@
-module(gleam@order).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/gleam/order.gleam").
-export([negate/1, to_int/1, compare/2, reverse/1, break_tie/2, lazy_break_tie/2]).
-export_type([order/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type order() :: lt | eq | gt.
-file("src/gleam/order.gleam", 35).
?DOC(
" Inverts an order, so less-than becomes greater-than and greater-than\n"
" becomes less-than.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" negate(Lt)\n"
" // -> Gt\n"
" ```\n"
"\n"
" ```gleam\n"
" negate(Eq)\n"
" // -> Eq\n"
" ```\n"
"\n"
" ```gleam\n"
" negate(Gt)\n"
" // -> Lt\n"
" ```\n"
).
-spec negate(order()) -> order().
negate(Order) ->
case Order of
lt ->
gt;
eq ->
eq;
gt ->
lt
end.
-file("src/gleam/order.gleam", 62).
?DOC(
" Produces a numeric representation of the order.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_int(Lt)\n"
" // -> -1\n"
" ```\n"
"\n"
" ```gleam\n"
" to_int(Eq)\n"
" // -> 0\n"
" ```\n"
"\n"
" ```gleam\n"
" to_int(Gt)\n"
" // -> 1\n"
" ```\n"
).
-spec to_int(order()) -> integer().
to_int(Order) ->
case Order of
lt ->
-1;
eq ->
0;
gt ->
1
end.
-file("src/gleam/order.gleam", 79).
?DOC(
" Compares two `Order` values to one another, producing a new `Order`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" compare(Eq, with: Lt)\n"
" // -> Gt\n"
" ```\n"
).
-spec compare(order(), order()) -> order().
compare(A, B) ->
case {A, B} of
{X, Y} when X =:= Y ->
eq;
{lt, _} ->
lt;
{eq, gt} ->
lt;
{_, _} ->
gt
end.
-file("src/gleam/order.gleam", 100).
?DOC(
" Inverts an ordering function, so less-than becomes greater-than and greater-than\n"
" becomes less-than.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" import gleam/int\n"
" import gleam/list\n"
"\n"
" list.sort([1, 5, 4], by: reverse(int.compare))\n"
" // -> [5, 4, 1]\n"
" ```\n"
).
-spec reverse(fun((I, I) -> order())) -> fun((I, I) -> order()).
reverse(Orderer) ->
fun(A, B) -> Orderer(B, A) end.
-file("src/gleam/order.gleam", 122).
?DOC(
" Return a fallback `Order` in case the first argument is `Eq`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" import gleam/int\n"
"\n"
" break_tie(in: int.compare(1, 1), with: Lt)\n"
" // -> Lt\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/int\n"
"\n"
" break_tie(in: int.compare(1, 0), with: Eq)\n"
" // -> Gt\n"
" ```\n"
).
-spec break_tie(order(), order()) -> order().
break_tie(Order, Other) ->
case Order of
lt ->
Order;
gt ->
Order;
eq ->
Other
end.
-file("src/gleam/order.gleam", 151).
?DOC(
" Invokes a fallback function returning an `Order` in case the first argument\n"
" is `Eq`.\n"
"\n"
" This can be useful when the fallback comparison might be expensive and it\n"
" needs to be delayed until strictly necessary.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" import gleam/int\n"
"\n"
" lazy_break_tie(in: int.compare(1, 1), with: fn() { Lt })\n"
" // -> Lt\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/int\n"
"\n"
" lazy_break_tie(in: int.compare(1, 0), with: fn() { Eq })\n"
" // -> Gt\n"
" ```\n"
).
-spec lazy_break_tie(order(), fun(() -> order())) -> order().
lazy_break_tie(Order, Comparison) ->
case Order of
lt ->
Order;
gt ->
Order;
eq ->
Comparison()
end.

View file

@ -0,0 +1,110 @@
-module(gleam@pair).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/gleam/pair.gleam").
-export([first/1, second/1, swap/1, map_first/2, map_second/2, new/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-file("src/gleam/pair.gleam", 10).
?DOC(
" Returns the first element in a pair.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" first(#(1, 2))\n"
" // -> 1\n"
" ```\n"
).
-spec first({COH, any()}) -> COH.
first(Pair) ->
{A, _} = Pair,
A.
-file("src/gleam/pair.gleam", 24).
?DOC(
" Returns the second element in a pair.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" second(#(1, 2))\n"
" // -> 2\n"
" ```\n"
).
-spec second({any(), COK}) -> COK.
second(Pair) ->
{_, A} = Pair,
A.
-file("src/gleam/pair.gleam", 38).
?DOC(
" Returns a new pair with the elements swapped.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" swap(#(1, 2))\n"
" // -> #(2, 1)\n"
" ```\n"
).
-spec swap({COL, COM}) -> {COM, COL}.
swap(Pair) ->
{A, B} = Pair,
{B, A}.
-file("src/gleam/pair.gleam", 53).
?DOC(
" Returns a new pair with the first element having had `with` applied to\n"
" it.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" #(1, 2) |> map_first(fn(n) { n * 2 })\n"
" // -> #(2, 2)\n"
" ```\n"
).
-spec map_first({CON, COO}, fun((CON) -> COP)) -> {COP, COO}.
map_first(Pair, Fun) ->
{A, B} = Pair,
{Fun(A), B}.
-file("src/gleam/pair.gleam", 68).
?DOC(
" Returns a new pair with the second element having had `with` applied to\n"
" it.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" #(1, 2) |> map_second(fn(n) { n * 2 })\n"
" // -> #(1, 4)\n"
" ```\n"
).
-spec map_second({COQ, COR}, fun((COR) -> COS)) -> {COQ, COS}.
map_second(Pair, Fun) ->
{A, B} = Pair,
{A, Fun(B)}.
-file("src/gleam/pair.gleam", 83).
?DOC(
" Returns a new pair with the given elements. This can also be done using the dedicated\n"
" syntax instead: `new(1, 2) == #(1, 2)`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new(1, 2)\n"
" // -> #(1, 2)\n"
" ```\n"
).
-spec new(COT, COU) -> {COT, COU}.
new(First, Second) ->
{First, Second}.

View file

@ -0,0 +1,566 @@
-module(gleam@result).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/gleam/result.gleam").
-export([is_ok/1, is_error/1, map/2, map_error/2, flatten/1, 'try'/2, then/2, unwrap/2, lazy_unwrap/2, unwrap_error/2, unwrap_both/1, 'or'/2, lazy_or/2, all/1, partition/1, replace/2, replace_error/2, values/1, try_recover/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Result represents the result of something that may succeed or not.\n"
" `Ok` means it was successful, `Error` means it was not successful.\n"
).
-file("src/gleam/result.gleam", 20).
?DOC(
" Checks whether the result is an `Ok` value.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" is_ok(Ok(1))\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" is_ok(Error(Nil))\n"
" // -> False\n"
" ```\n"
).
-spec is_ok({ok, any()} | {error, any()}) -> boolean().
is_ok(Result) ->
case Result of
{error, _} ->
false;
{ok, _} ->
true
end.
-file("src/gleam/result.gleam", 41).
?DOC(
" Checks whether the result is an `Error` value.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" is_error(Ok(1))\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" is_error(Error(Nil))\n"
" // -> True\n"
" ```\n"
).
-spec is_error({ok, any()} | {error, any()}) -> boolean().
is_error(Result) ->
case Result of
{ok, _} ->
false;
{error, _} ->
true
end.
-file("src/gleam/result.gleam", 66).
?DOC(
" Updates a value held within the `Ok` of a result by calling a given function\n"
" on it.\n"
"\n"
" If the result is an `Error` rather than `Ok` the function is not called and the\n"
" result stays the same.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" map(over: Ok(1), with: fn(x) { x + 1 })\n"
" // -> Ok(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" map(over: Error(1), with: fn(x) { x + 1 })\n"
" // -> Error(1)\n"
" ```\n"
).
-spec map({ok, CPE} | {error, CPF}, fun((CPE) -> CPI)) -> {ok, CPI} |
{error, CPF}.
map(Result, Fun) ->
case Result of
{ok, X} ->
{ok, Fun(X)};
{error, E} ->
{error, E}
end.
-file("src/gleam/result.gleam", 91).
?DOC(
" Updates a value held within the `Error` of a result by calling a given function\n"
" on it.\n"
"\n"
" If the result is `Ok` rather than `Error` the function is not called and the\n"
" result stays the same.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" map_error(over: Error(1), with: fn(x) { x + 1 })\n"
" // -> Error(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" map_error(over: Ok(1), with: fn(x) { x + 1 })\n"
" // -> Ok(1)\n"
" ```\n"
).
-spec map_error({ok, CPL} | {error, CPM}, fun((CPM) -> CPP)) -> {ok, CPL} |
{error, CPP}.
map_error(Result, Fun) ->
case Result of
{ok, X} ->
{ok, X};
{error, Error} ->
{error, Fun(Error)}
end.
-file("src/gleam/result.gleam", 120).
?DOC(
" Merges a nested `Result` into a single layer.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" flatten(Ok(Ok(1)))\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" flatten(Ok(Error(\"\")))\n"
" // -> Error(\"\")\n"
" ```\n"
"\n"
" ```gleam\n"
" flatten(Error(Nil))\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec flatten({ok, {ok, CPS} | {error, CPT}} | {error, CPT}) -> {ok, CPS} |
{error, CPT}.
flatten(Result) ->
case Result of
{ok, X} ->
X;
{error, Error} ->
{error, Error}
end.
-file("src/gleam/result.gleam", 158).
?DOC(
" \"Updates\" an `Ok` result by passing its value to a function that yields a result,\n"
" and returning the yielded result. (This may \"replace\" the `Ok` with an `Error`.)\n"
"\n"
" If the input is an `Error` rather than an `Ok`, the function is not called and\n"
" the original `Error` is returned.\n"
"\n"
" This function is the equivalent of calling `map` followed by `flatten`, and\n"
" it is useful for chaining together multiple functions that may fail.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" try(Ok(1), fn(x) { Ok(x + 1) })\n"
" // -> Ok(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" try(Ok(1), fn(x) { Ok(#(\"a\", x)) })\n"
" // -> Ok(#(\"a\", 1))\n"
" ```\n"
"\n"
" ```gleam\n"
" try(Ok(1), fn(_) { Error(\"Oh no\") })\n"
" // -> Error(\"Oh no\")\n"
" ```\n"
"\n"
" ```gleam\n"
" try(Error(Nil), fn(x) { Ok(x + 1) })\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec 'try'({ok, CQA} | {error, CQB}, fun((CQA) -> {ok, CQE} | {error, CQB})) -> {ok,
CQE} |
{error, CQB}.
'try'(Result, Fun) ->
case Result of
{ok, X} ->
Fun(X);
{error, E} ->
{error, E}
end.
-file("src/gleam/result.gleam", 169).
-spec then({ok, CQJ} | {error, CQK}, fun((CQJ) -> {ok, CQN} | {error, CQK})) -> {ok,
CQN} |
{error, CQK}.
then(Result, Fun) ->
'try'(Result, Fun).
-file("src/gleam/result.gleam", 191).
?DOC(
" Extracts the `Ok` value from a result, returning a default value if the result\n"
" is an `Error`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" unwrap(Ok(1), 0)\n"
" // -> 1\n"
" ```\n"
"\n"
" ```gleam\n"
" unwrap(Error(\"\"), 0)\n"
" // -> 0\n"
" ```\n"
).
-spec unwrap({ok, CQS} | {error, any()}, CQS) -> CQS.
unwrap(Result, Default) ->
case Result of
{ok, V} ->
V;
{error, _} ->
Default
end.
-file("src/gleam/result.gleam", 213).
?DOC(
" Extracts the `Ok` value from a result, evaluating the default function if the result\n"
" is an `Error`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" lazy_unwrap(Ok(1), fn() { 0 })\n"
" // -> 1\n"
" ```\n"
"\n"
" ```gleam\n"
" lazy_unwrap(Error(\"\"), fn() { 0 })\n"
" // -> 0\n"
" ```\n"
).
-spec lazy_unwrap({ok, CQW} | {error, any()}, fun(() -> CQW)) -> CQW.
lazy_unwrap(Result, Default) ->
case Result of
{ok, V} ->
V;
{error, _} ->
Default()
end.
-file("src/gleam/result.gleam", 235).
?DOC(
" Extracts the `Error` value from a result, returning a default value if the result\n"
" is an `Ok`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" unwrap_error(Error(1), 0)\n"
" // -> 1\n"
" ```\n"
"\n"
" ```gleam\n"
" unwrap_error(Ok(\"\"), 0)\n"
" // -> 0\n"
" ```\n"
).
-spec unwrap_error({ok, any()} | {error, CRB}, CRB) -> CRB.
unwrap_error(Result, Default) ->
case Result of
{ok, _} ->
Default;
{error, E} ->
E
end.
-file("src/gleam/result.gleam", 257).
?DOC(
" Extracts the inner value from a result. Both the value and error must be of\n"
" the same type.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" unwrap_both(Error(1))\n"
" // -> 1\n"
" ```\n"
"\n"
" ```gleam\n"
" unwrap_both(Ok(2))\n"
" // -> 2\n"
" ```\n"
).
-spec unwrap_both({ok, CRE} | {error, CRE}) -> CRE.
unwrap_both(Result) ->
case Result of
{ok, A} ->
A;
{error, A@1} ->
A@1
end.
-file("src/gleam/result.gleam", 288).
?DOC(
" Returns the first value if it is `Ok`, otherwise returns the second value.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" or(Ok(1), Ok(2))\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" or(Ok(1), Error(\"Error 2\"))\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" or(Error(\"Error 1\"), Ok(2))\n"
" // -> Ok(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" or(Error(\"Error 1\"), Error(\"Error 2\"))\n"
" // -> Error(\"Error 2\")\n"
" ```\n"
).
-spec 'or'({ok, CRH} | {error, CRI}, {ok, CRH} | {error, CRI}) -> {ok, CRH} |
{error, CRI}.
'or'(First, Second) ->
case First of
{ok, _} ->
First;
{error, _} ->
Second
end.
-file("src/gleam/result.gleam", 321).
?DOC(
" Returns the first value if it is `Ok`, otherwise evaluates the given function for a fallback value.\n"
"\n"
" If you need access to the initial error value, use `result.try_recover`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" lazy_or(Ok(1), fn() { Ok(2) })\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" lazy_or(Ok(1), fn() { Error(\"Error 2\") })\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" lazy_or(Error(\"Error 1\"), fn() { Ok(2) })\n"
" // -> Ok(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" lazy_or(Error(\"Error 1\"), fn() { Error(\"Error 2\") })\n"
" // -> Error(\"Error 2\")\n"
" ```\n"
).
-spec lazy_or({ok, CRP} | {error, CRQ}, fun(() -> {ok, CRP} | {error, CRQ})) -> {ok,
CRP} |
{error, CRQ}.
lazy_or(First, Second) ->
case First of
{ok, _} ->
First;
{error, _} ->
Second()
end.
-file("src/gleam/result.gleam", 347).
?DOC(
" Combines a list of results into a single result.\n"
" If all elements in the list are `Ok` then returns an `Ok` holding the list of values.\n"
" If any element is `Error` then returns the first error.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" all([Ok(1), Ok(2)])\n"
" // -> Ok([1, 2])\n"
" ```\n"
"\n"
" ```gleam\n"
" all([Ok(1), Error(\"e\")])\n"
" // -> Error(\"e\")\n"
" ```\n"
).
-spec all(list({ok, CRX} | {error, CRY})) -> {ok, list(CRX)} | {error, CRY}.
all(Results) ->
gleam@list:try_map(Results, fun(Result) -> Result end).
-file("src/gleam/result.gleam", 367).
-spec partition_loop(list({ok, CSM} | {error, CSN}), list(CSM), list(CSN)) -> {list(CSM),
list(CSN)}.
partition_loop(Results, Oks, Errors) ->
case Results of
[] ->
{Oks, Errors};
[{ok, A} | Rest] ->
partition_loop(Rest, [A | Oks], Errors);
[{error, E} | Rest@1] ->
partition_loop(Rest@1, Oks, [E | Errors])
end.
-file("src/gleam/result.gleam", 363).
?DOC(
" Given a list of results, returns a pair where the first element is a list\n"
" of all the values inside `Ok` and the second element is a list with all the\n"
" values inside `Error`. The values in both lists appear in reverse order with\n"
" respect to their position in the original list of results.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" partition([Ok(1), Error(\"a\"), Error(\"b\"), Ok(2)])\n"
" // -> #([2, 1], [\"b\", \"a\"])\n"
" ```\n"
).
-spec partition(list({ok, CSF} | {error, CSG})) -> {list(CSF), list(CSG)}.
partition(Results) ->
partition_loop(Results, [], []).
-file("src/gleam/result.gleam", 389).
?DOC(
" Replace the value within a result\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" replace(Ok(1), Nil)\n"
" // -> Ok(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" replace(Error(1), Nil)\n"
" // -> Error(1)\n"
" ```\n"
).
-spec replace({ok, any()} | {error, CSV}, CSY) -> {ok, CSY} | {error, CSV}.
replace(Result, Value) ->
case Result of
{ok, _} ->
{ok, Value};
{error, Error} ->
{error, Error}
end.
-file("src/gleam/result.gleam", 410).
?DOC(
" Replace the error within a result\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" replace_error(Error(1), Nil)\n"
" // -> Error(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" replace_error(Ok(1), Nil)\n"
" // -> Ok(1)\n"
" ```\n"
).
-spec replace_error({ok, CTB} | {error, any()}, CTF) -> {ok, CTB} | {error, CTF}.
replace_error(Result, Error) ->
case Result of
{ok, X} ->
{ok, X};
{error, _} ->
{error, Error}
end.
-file("src/gleam/result.gleam", 426).
?DOC(
" Given a list of results, returns only the values inside `Ok`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" values([Ok(1), Error(\"a\"), Ok(3)])\n"
" // -> [1, 3]\n"
" ```\n"
).
-spec values(list({ok, CTI} | {error, any()})) -> list(CTI).
values(Results) ->
gleam@list:filter_map(Results, fun(Result) -> Result end).
-file("src/gleam/result.gleam", 459).
?DOC(
" Updates a value held within the `Error` of a result by calling a given function\n"
" on it, where the given function also returns a result. The two results are\n"
" then merged together into one result.\n"
"\n"
" If the result is an `Ok` rather than `Error` the function is not called and the\n"
" result stays the same.\n"
"\n"
" This function is useful for chaining together computations that may fail\n"
" and trying to recover from possible errors.\n"
"\n"
" If you do not need access to the initial error value, use `result.lazy_or`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" Ok(1) |> try_recover(with: fn(_) { Error(\"failed to recover\") })\n"
" // -> Ok(1)\n"
" ```\n"
"\n"
" ```gleam\n"
" Error(1) |> try_recover(with: fn(error) { Ok(error + 1) })\n"
" // -> Ok(2)\n"
" ```\n"
"\n"
" ```gleam\n"
" Error(1) |> try_recover(with: fn(error) { Error(\"failed to recover\") })\n"
" // -> Error(\"failed to recover\")\n"
" ```\n"
).
-spec try_recover(
{ok, CTO} | {error, CTP},
fun((CTP) -> {ok, CTO} | {error, CTS})
) -> {ok, CTO} | {error, CTS}.
try_recover(Result, Fun) ->
case Result of
{ok, Value} ->
{ok, Value};
{error, Error} ->
Fun(Error)
end.

View file

@ -0,0 +1,429 @@
-module(gleam@set).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/gleam/set.gleam").
-export([new/0, size/1, is_empty/1, contains/2, delete/2, to_list/1, fold/3, filter/2, drop/2, take/2, intersection/2, difference/2, is_subset/2, is_disjoint/2, each/2, insert/2, from_list/1, map/2, union/2, symmetric_difference/2]).
-export_type([set/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-opaque set(CYN) :: {set, gleam@dict:dict(CYN, list(nil))}.
-file("src/gleam/set.gleam", 32).
?DOC(" Creates a new empty set.\n").
-spec new() -> set(any()).
new() ->
{set, maps:new()}.
-file("src/gleam/set.gleam", 50).
?DOC(
" Gets the number of members in a set.\n"
"\n"
" This function runs in constant time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new()\n"
" |> insert(1)\n"
" |> insert(2)\n"
" |> size\n"
" // -> 2\n"
" ```\n"
).
-spec size(set(any())) -> integer().
size(Set) ->
maps:size(erlang:element(2, Set)).
-file("src/gleam/set.gleam", 68).
?DOC(
" Determines whether or not the set is empty.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new() |> is_empty\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" new() |> insert(1) |> is_empty\n"
" // -> False\n"
" ```\n"
).
-spec is_empty(set(any())) -> boolean().
is_empty(Set) ->
Set =:= new().
-file("src/gleam/set.gleam", 110).
?DOC(
" Checks whether a set contains a given member.\n"
"\n"
" This function runs in logarithmic time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new()\n"
" |> insert(2)\n"
" |> contains(2)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" new()\n"
" |> insert(2)\n"
" |> contains(1)\n"
" // -> False\n"
" ```\n"
).
-spec contains(set(CYY), CYY) -> boolean().
contains(Set, Member) ->
_pipe = erlang:element(2, Set),
_pipe@1 = gleam_stdlib:map_get(_pipe, Member),
gleam@result:is_ok(_pipe@1).
-file("src/gleam/set.gleam", 131).
?DOC(
" Removes a member from a set. If the set does not contain the member then\n"
" the set is returned unchanged.\n"
"\n"
" This function runs in logarithmic time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new()\n"
" |> insert(2)\n"
" |> delete(2)\n"
" |> contains(1)\n"
" // -> False\n"
" ```\n"
).
-spec delete(set(CZA), CZA) -> set(CZA).
delete(Set, Member) ->
{set, gleam@dict:delete(erlang:element(2, Set), Member)}.
-file("src/gleam/set.gleam", 149).
?DOC(
" Converts the set into a list of the contained members.\n"
"\n"
" The list has no specific ordering, any unintentional ordering may change in\n"
" future versions of Gleam or Erlang.\n"
"\n"
" This function runs in linear time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new() |> insert(2) |> to_list\n"
" // -> [2]\n"
" ```\n"
).
-spec to_list(set(CZD)) -> list(CZD).
to_list(Set) ->
maps:keys(erlang:element(2, Set)).
-file("src/gleam/set.gleam", 190).
?DOC(
" Combines all entries into a single value by calling a given function on each\n"
" one.\n"
"\n"
" Sets are not ordered so the values are not returned in any specific order.\n"
" Do not write code that relies on the order entries are used by this\n"
" function as it may change in later versions of Gleam or Erlang.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" from_list([1, 3, 9])\n"
" |> fold(0, fn(accumulator, member) { accumulator + member })\n"
" // -> 13\n"
" ```\n"
).
-spec fold(set(CZJ), CZL, fun((CZL, CZJ) -> CZL)) -> CZL.
fold(Set, Initial, Reducer) ->
gleam@dict:fold(
erlang:element(2, Set),
Initial,
fun(A, K, _) -> Reducer(A, K) end
).
-file("src/gleam/set.gleam", 214).
?DOC(
" Creates a new set from an existing set, minus any members that a given\n"
" function returns `False` for.\n"
"\n"
" This function runs in loglinear time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" import gleam/int\n"
"\n"
" from_list([1, 4, 6, 3, 675, 44, 67])\n"
" |> filter(keeping: int.is_even)\n"
" |> to_list\n"
" // -> [4, 6, 44]\n"
" ```\n"
).
-spec filter(set(CZM), fun((CZM) -> boolean())) -> set(CZM).
filter(Set, Predicate) ->
{set,
gleam@dict:filter(erlang:element(2, Set), fun(M, _) -> Predicate(M) end)}.
-file("src/gleam/set.gleam", 249).
?DOC(
" Creates a new set from a given set with all the same entries except any\n"
" entry found on the given list.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([1, 2, 3, 4])\n"
" |> drop([1, 3])\n"
" |> to_list\n"
" // -> [2, 4]\n"
" ```\n"
).
-spec drop(set(CZT), list(CZT)) -> set(CZT).
drop(Set, Disallowed) ->
gleam@list:fold(Disallowed, Set, fun delete/2).
-file("src/gleam/set.gleam", 267).
?DOC(
" Creates a new set from a given set, only including any members which are in\n"
" a given list.\n"
"\n"
" This function runs in loglinear time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([1, 2, 3])\n"
" |> take([1, 3, 5])\n"
" |> to_list\n"
" // -> [1, 3]\n"
" ```\n"
).
-spec take(set(CZX), list(CZX)) -> set(CZX).
take(Set, Desired) ->
{set, gleam@dict:take(erlang:element(2, Set), Desired)}.
-file("src/gleam/set.gleam", 287).
-spec order(set(DAF), set(DAF)) -> {set(DAF), set(DAF)}.
order(First, Second) ->
case maps:size(erlang:element(2, First)) > maps:size(
erlang:element(2, Second)
) of
true ->
{First, Second};
false ->
{Second, First}
end.
-file("src/gleam/set.gleam", 305).
?DOC(
" Creates a new set that contains members that are present in both given sets.\n"
"\n"
" This function runs in loglinear time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" intersection(from_list([1, 2]), from_list([2, 3])) |> to_list\n"
" // -> [2]\n"
" ```\n"
).
-spec intersection(set(DAK), set(DAK)) -> set(DAK).
intersection(First, Second) ->
{Larger, Smaller} = order(First, Second),
take(Larger, to_list(Smaller)).
-file("src/gleam/set.gleam", 323).
?DOC(
" Creates a new set that contains members that are present in the first set\n"
" but not the second.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" difference(from_list([1, 2]), from_list([2, 3, 4])) |> to_list\n"
" // -> [1]\n"
" ```\n"
).
-spec difference(set(DAO), set(DAO)) -> set(DAO).
difference(First, Second) ->
drop(First, to_list(Second)).
-file("src/gleam/set.gleam", 344).
?DOC(
" Determines if a set is fully contained by another.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" is_subset(from_list([1]), from_list([1, 2]))\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" is_subset(from_list([1, 2, 3]), from_list([3, 4, 5]))\n"
" // -> False\n"
" ```\n"
).
-spec is_subset(set(DAS), set(DAS)) -> boolean().
is_subset(First, Second) ->
intersection(First, Second) =:= First.
-file("src/gleam/set.gleam", 362).
?DOC(
" Determines if two sets contain no common members\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" is_disjoint(from_list([1, 2, 3]), from_list([4, 5, 6]))\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" is_disjoint(from_list([1, 2, 3]), from_list([3, 4, 5]))\n"
" // -> False\n"
" ```\n"
).
-spec is_disjoint(set(DAV), set(DAV)) -> boolean().
is_disjoint(First, Second) ->
intersection(First, Second) =:= new().
-file("src/gleam/set.gleam", 402).
?DOC(
" Calls a function for each member in a set, discarding the return\n"
" value.\n"
"\n"
" Useful for producing a side effect for every item of a set.\n"
"\n"
" ```gleam\n"
" let set = from_list([\"apple\", \"banana\", \"cherry\"])\n"
"\n"
" each(set, io.println)\n"
" // -> Nil\n"
" // apple\n"
" // banana\n"
" // cherry\n"
" ```\n"
"\n"
" The order of elements in the iteration is an implementation detail that\n"
" should not be relied upon.\n"
).
-spec each(set(DBC), fun((DBC) -> any())) -> nil.
each(Set, Fun) ->
fold(
Set,
nil,
fun(Nil, Member) ->
Fun(Member),
Nil
end
).
-file("src/gleam/set.gleam", 86).
?DOC(
" Inserts an member into the set.\n"
"\n"
" This function runs in logarithmic time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" new()\n"
" |> insert(1)\n"
" |> insert(2)\n"
" |> size\n"
" // -> 2\n"
" ```\n"
).
-spec insert(set(CYV), CYV) -> set(CYV).
insert(Set, Member) ->
{set, gleam@dict:insert(erlang:element(2, Set), Member, [])}.
-file("src/gleam/set.gleam", 167).
?DOC(
" Creates a new set of the members in a given list.\n"
"\n"
" This function runs in loglinear time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" import gleam/int\n"
" import gleam/list\n"
"\n"
" [1, 1, 2, 4, 3, 2] |> from_list |> to_list |> list.sort(by: int.compare)\n"
" // -> [1, 2, 3, 4]\n"
" ```\n"
).
-spec from_list(list(CZG)) -> set(CZG).
from_list(Members) ->
Dict = gleam@list:fold(
Members,
maps:new(),
fun(M, K) -> gleam@dict:insert(M, K, []) end
),
{set, Dict}.
-file("src/gleam/set.gleam", 232).
?DOC(
" Creates a new set from a given set with the result of applying the given\n"
" function to each member.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_list([1, 2, 3, 4])\n"
" |> map(with: fn(x) { x * 2 })\n"
" |> to_list\n"
" // -> [2, 4, 6, 8]\n"
" ```\n"
).
-spec map(set(CZP), fun((CZP) -> CZR)) -> set(CZR).
map(Set, Fun) ->
fold(Set, new(), fun(Acc, Member) -> insert(Acc, Fun(Member)) end).
-file("src/gleam/set.gleam", 282).
?DOC(
" Creates a new set that contains all members of both given sets.\n"
"\n"
" This function runs in loglinear time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" union(from_list([1, 2]), from_list([2, 3])) |> to_list\n"
" // -> [1, 2, 3]\n"
" ```\n"
).
-spec union(set(DAB), set(DAB)) -> set(DAB).
union(First, Second) ->
{Larger, Smaller} = order(First, Second),
fold(Smaller, Larger, fun insert/2).
-file("src/gleam/set.gleam", 374).
?DOC(
" Creates a new set that contains members that are present in either set, but\n"
" not both.\n"
"\n"
" ```gleam\n"
" symmetric_difference(from_list([1, 2, 3]), from_list([3, 4])) |> to_list\n"
" // -> [1, 2, 4]\n"
" ```\n"
).
-spec symmetric_difference(set(DAY), set(DAY)) -> set(DAY).
symmetric_difference(First, Second) ->
difference(union(First, Second), intersection(First, Second)).

View file

@ -0,0 +1,957 @@
-module(gleam@string).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/gleam/string.gleam").
-export([is_empty/1, length/1, reverse/1, replace/3, lowercase/1, uppercase/1, compare/2, slice/3, crop/2, drop_end/2, contains/2, starts_with/2, ends_with/2, split_once/2, append/2, concat/1, repeat/2, join/2, pad_start/3, pad_end/3, trim_start/1, trim_end/1, trim/1, pop_grapheme/1, drop_start/2, to_graphemes/1, split/2, to_utf_codepoints/1, from_utf_codepoints/1, utf_codepoint/1, utf_codepoint_to_int/1, to_option/1, first/1, last/1, capitalise/1, inspect/1, byte_size/1]).
-export_type([direction/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Strings in Gleam are UTF-8 binaries. They can be written in your code as\n"
" text surrounded by `\"double quotes\"`.\n"
).
-type direction() :: leading | trailing.
-file("src/gleam/string.gleam", 23).
?DOC(
" Determines if a `String` is empty.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" is_empty(\"\")\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" is_empty(\"the world\")\n"
" // -> False\n"
" ```\n"
).
-spec is_empty(binary()) -> boolean().
is_empty(Str) ->
Str =:= <<""/utf8>>.
-file("src/gleam/string.gleam", 51).
?DOC(
" Gets the number of grapheme clusters in a given `String`.\n"
"\n"
" This function has to iterate across the whole string to count the number of\n"
" graphemes, so it runs in linear time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" length(\"Gleam\")\n"
" // -> 5\n"
" ```\n"
"\n"
" ```gleam\n"
" length(\"ß↑e̊\")\n"
" // -> 3\n"
" ```\n"
"\n"
" ```gleam\n"
" length(\"\")\n"
" // -> 0\n"
" ```\n"
).
-spec length(binary()) -> integer().
length(String) ->
string:length(String).
-file("src/gleam/string.gleam", 65).
?DOC(
" Reverses a `String`.\n"
"\n"
" This function has to iterate across the whole `String` so it runs in linear\n"
" time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" reverse(\"stressed\")\n"
" // -> \"desserts\"\n"
" ```\n"
).
-spec reverse(binary()) -> binary().
reverse(String) ->
_pipe = String,
_pipe@1 = gleam_stdlib:identity(_pipe),
_pipe@2 = string:reverse(_pipe@1),
unicode:characters_to_binary(_pipe@2).
-file("src/gleam/string.gleam", 86).
?DOC(
" Creates a new `String` by replacing all occurrences of a given substring.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" replace(\"www.example.com\", each: \".\", with: \"-\")\n"
" // -> \"www-example-com\"\n"
" ```\n"
"\n"
" ```gleam\n"
" replace(\"a,b,c,d,e\", each: \",\", with: \"/\")\n"
" // -> \"a/b/c/d/e\"\n"
" ```\n"
).
-spec replace(binary(), binary(), binary()) -> binary().
replace(String, Pattern, Substitute) ->
_pipe = String,
_pipe@1 = gleam_stdlib:identity(_pipe),
_pipe@2 = gleam_stdlib:string_replace(_pipe@1, Pattern, Substitute),
unicode:characters_to_binary(_pipe@2).
-file("src/gleam/string.gleam", 111).
?DOC(
" Creates a new `String` with all the graphemes in the input `String` converted to\n"
" lowercase.\n"
"\n"
" Useful for case-insensitive comparisons.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" lowercase(\"X-FILES\")\n"
" // -> \"x-files\"\n"
" ```\n"
).
-spec lowercase(binary()) -> binary().
lowercase(String) ->
string:lowercase(String).
-file("src/gleam/string.gleam", 127).
?DOC(
" Creates a new `String` with all the graphemes in the input `String` converted to\n"
" uppercase.\n"
"\n"
" Useful for case-insensitive comparisons and VIRTUAL YELLING.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" uppercase(\"skinner\")\n"
" // -> \"SKINNER\"\n"
" ```\n"
).
-spec uppercase(binary()) -> binary().
uppercase(String) ->
string:uppercase(String).
-file("src/gleam/string.gleam", 145).
?DOC(
" Compares two `String`s to see which is \"larger\" by comparing their graphemes.\n"
"\n"
" This does not compare the size or length of the given `String`s.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" compare(\"Anthony\", \"Anthony\")\n"
" // -> order.Eq\n"
" ```\n"
"\n"
" ```gleam\n"
" compare(\"A\", \"B\")\n"
" // -> order.Lt\n"
" ```\n"
).
-spec compare(binary(), binary()) -> gleam@order:order().
compare(A, B) ->
case A =:= B of
true ->
eq;
_ ->
case gleam_stdlib:less_than(A, B) of
true ->
lt;
false ->
gt
end
end.
-file("src/gleam/string.gleam", 190).
?DOC(
" Takes a substring given a start grapheme index and a length. Negative indexes\n"
" are taken starting from the *end* of the list.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" slice(from: \"gleam\", at_index: 1, length: 2)\n"
" // -> \"le\"\n"
" ```\n"
"\n"
" ```gleam\n"
" slice(from: \"gleam\", at_index: 1, length: 10)\n"
" // -> \"leam\"\n"
" ```\n"
"\n"
" ```gleam\n"
" slice(from: \"gleam\", at_index: 10, length: 3)\n"
" // -> \"\"\n"
" ```\n"
"\n"
" ```gleam\n"
" slice(from: \"gleam\", at_index: -2, length: 2)\n"
" // -> \"am\"\n"
" ```\n"
"\n"
" ```gleam\n"
" slice(from: \"gleam\", at_index: -12, length: 2)\n"
" // -> \"\"\n"
" ```\n"
).
-spec slice(binary(), integer(), integer()) -> binary().
slice(String, Idx, Len) ->
case Len < 0 of
true ->
<<""/utf8>>;
false ->
case Idx < 0 of
true ->
Translated_idx = string:length(String) + Idx,
case Translated_idx < 0 of
true ->
<<""/utf8>>;
false ->
gleam_stdlib:slice(String, Translated_idx, Len)
end;
false ->
gleam_stdlib:slice(String, Idx, Len)
end
end.
-file("src/gleam/string.gleam", 223).
?DOC(
" Drops contents of the first `String` that occur before the second `String`.\n"
" If the `from` string does not contain the `before` string, `from` is returned unchanged.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" crop(from: \"The Lone Gunmen\", before: \"Lone\")\n"
" // -> \"Lone Gunmen\"\n"
" ```\n"
).
-spec crop(binary(), binary()) -> binary().
crop(String, Substring) ->
gleam_stdlib:crop_string(String, Substring).
-file("src/gleam/string.gleam", 254).
?DOC(
" Drops *n* graphemes from the end of a `String`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" drop_end(from: \"Cigarette Smoking Man\", up_to: 2)\n"
" // -> \"Cigarette Smoking M\"\n"
" ```\n"
).
-spec drop_end(binary(), integer()) -> binary().
drop_end(String, Num_graphemes) ->
case Num_graphemes < 0 of
true ->
String;
false ->
slice(String, 0, string:length(String) - Num_graphemes)
end.
-file("src/gleam/string.gleam", 282).
?DOC(
" Checks if the first `String` contains the second.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" contains(does: \"theory\", contain: \"ory\")\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" contains(does: \"theory\", contain: \"the\")\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" contains(does: \"theory\", contain: \"THE\")\n"
" // -> False\n"
" ```\n"
).
-spec contains(binary(), binary()) -> boolean().
contains(Haystack, Needle) ->
gleam_stdlib:contains_string(Haystack, Needle).
-file("src/gleam/string.gleam", 295).
?DOC(
" Checks whether the first `String` starts with the second one.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" starts_with(\"theory\", \"ory\")\n"
" // -> False\n"
" ```\n"
).
-spec starts_with(binary(), binary()) -> boolean().
starts_with(String, Prefix) ->
gleam_stdlib:string_starts_with(String, Prefix).
-file("src/gleam/string.gleam", 308).
?DOC(
" Checks whether the first `String` ends with the second one.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" ends_with(\"theory\", \"ory\")\n"
" // -> True\n"
" ```\n"
).
-spec ends_with(binary(), binary()) -> boolean().
ends_with(String, Suffix) ->
gleam_stdlib:string_ends_with(String, Suffix).
-file("src/gleam/string.gleam", 347).
?DOC(
" Splits a `String` a single time on the given substring.\n"
"\n"
" Returns an `Error` if substring not present.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" split_once(\"home/gleam/desktop/\", on: \"/\")\n"
" // -> Ok(#(\"home\", \"gleam/desktop/\"))\n"
" ```\n"
"\n"
" ```gleam\n"
" split_once(\"home/gleam/desktop/\", on: \"?\")\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec split_once(binary(), binary()) -> {ok, {binary(), binary()}} |
{error, nil}.
split_once(String, Substring) ->
case string:split(String, Substring) of
[First, Rest] ->
{ok, {First, Rest}};
_ ->
{error, nil}
end.
-file("src/gleam/string.gleam", 378).
?DOC(
" Creates a new `String` by joining two `String`s together.\n"
"\n"
" This function typically copies both `String`s and runs in linear time, but\n"
" the exact behaviour will depend on how the runtime you are using optimises\n"
" your code. Benchmark and profile your code if you need to understand its\n"
" performance better.\n"
"\n"
" If you are joining together large string and want to avoid copying any data\n"
" you may want to investigate using the [`string_tree`](../gleam/string_tree.html)\n"
" module.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" append(to: \"butter\", suffix: \"fly\")\n"
" // -> \"butterfly\"\n"
" ```\n"
).
-spec append(binary(), binary()) -> binary().
append(First, Second) ->
<<First/binary, Second/binary>>.
-file("src/gleam/string.gleam", 400).
-spec concat_loop(list(binary()), binary()) -> binary().
concat_loop(Strings, Accumulator) ->
case Strings of
[String | Strings@1] ->
concat_loop(Strings@1, <<Accumulator/binary, String/binary>>);
[] ->
Accumulator
end.
-file("src/gleam/string.gleam", 396).
?DOC(
" Creates a new `String` by joining many `String`s together.\n"
"\n"
" This function copies both `String`s and runs in linear time. If you find\n"
" yourself joining `String`s frequently consider using the [`string_tree`](../gleam/string_tree.html)\n"
" module as it can append `String`s much faster!\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" concat([\"never\", \"the\", \"less\"])\n"
" // -> \"nevertheless\"\n"
" ```\n"
).
-spec concat(list(binary())) -> binary().
concat(Strings) ->
erlang:list_to_binary(Strings).
-file("src/gleam/string.gleam", 422).
-spec repeat_loop(binary(), integer(), binary()) -> binary().
repeat_loop(String, Times, Acc) ->
case Times =< 0 of
true ->
Acc;
false ->
repeat_loop(String, Times - 1, <<Acc/binary, String/binary>>)
end.
-file("src/gleam/string.gleam", 418).
?DOC(
" Creates a new `String` by repeating a `String` a given number of times.\n"
"\n"
" This function runs in linear time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" repeat(\"ha\", times: 3)\n"
" // -> \"hahaha\"\n"
" ```\n"
).
-spec repeat(binary(), integer()) -> binary().
repeat(String, Times) ->
repeat_loop(String, Times, <<""/utf8>>).
-file("src/gleam/string.gleam", 447).
-spec join_loop(list(binary()), binary(), binary()) -> binary().
join_loop(Strings, Separator, Accumulator) ->
case Strings of
[] ->
Accumulator;
[String | Strings@1] ->
join_loop(
Strings@1,
Separator,
<<<<Accumulator/binary, Separator/binary>>/binary,
String/binary>>
)
end.
-file("src/gleam/string.gleam", 440).
?DOC(
" Joins many `String`s together with a given separator.\n"
"\n"
" This function runs in linear time.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" join([\"home\",\"evan\",\"Desktop\"], with: \"/\")\n"
" // -> \"home/evan/Desktop\"\n"
" ```\n"
).
-spec join(list(binary()), binary()) -> binary().
join(Strings, Separator) ->
case Strings of
[] ->
<<""/utf8>>;
[First | Rest] ->
join_loop(Rest, Separator, First)
end.
-file("src/gleam/string.gleam", 525).
-spec padding(integer(), binary()) -> binary().
padding(Size, Pad_string) ->
Pad_string_length = string:length(Pad_string),
Num_pads = case Pad_string_length of
0 -> 0;
Gleam@denominator -> Size div Gleam@denominator
end,
Extra = case Pad_string_length of
0 -> 0;
Gleam@denominator@1 -> Size rem Gleam@denominator@1
end,
<<(repeat(Pad_string, Num_pads))/binary,
(slice(Pad_string, 0, Extra))/binary>>.
-file("src/gleam/string.gleam", 478).
?DOC(
" Pads the start of a `String` until it has a given length.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" pad_start(\"121\", to: 5, with: \".\")\n"
" // -> \"..121\"\n"
" ```\n"
"\n"
" ```gleam\n"
" pad_start(\"121\", to: 3, with: \".\")\n"
" // -> \"121\"\n"
" ```\n"
"\n"
" ```gleam\n"
" pad_start(\"121\", to: 2, with: \".\")\n"
" // -> \"121\"\n"
" ```\n"
).
-spec pad_start(binary(), integer(), binary()) -> binary().
pad_start(String, Desired_length, Pad_string) ->
Current_length = string:length(String),
To_pad_length = Desired_length - Current_length,
case To_pad_length =< 0 of
true ->
String;
false ->
<<(padding(To_pad_length, Pad_string))/binary, String/binary>>
end.
-file("src/gleam/string.gleam", 511).
?DOC(
" Pads the end of a `String` until it has a given length.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" pad_end(\"123\", to: 5, with: \".\")\n"
" // -> \"123..\"\n"
" ```\n"
"\n"
" ```gleam\n"
" pad_end(\"123\", to: 3, with: \".\")\n"
" // -> \"123\"\n"
" ```\n"
"\n"
" ```gleam\n"
" pad_end(\"123\", to: 2, with: \".\")\n"
" // -> \"123\"\n"
" ```\n"
).
-spec pad_end(binary(), integer(), binary()) -> binary().
pad_end(String, Desired_length, Pad_string) ->
Current_length = string:length(String),
To_pad_length = Desired_length - Current_length,
case To_pad_length =< 0 of
true ->
String;
false ->
<<String/binary, (padding(To_pad_length, Pad_string))/binary>>
end.
-file("src/gleam/string.gleam", 569).
?DOC(
" Removes whitespace at the start of a `String`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" trim_start(\" hats \\n\")\n"
" // -> \"hats \\n\"\n"
" ```\n"
).
-spec trim_start(binary()) -> binary().
trim_start(String) ->
string:trim(String, leading).
-file("src/gleam/string.gleam", 583).
?DOC(
" Removes whitespace at the end of a `String`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" trim_end(\" hats \\n\")\n"
" // -> \" hats\"\n"
" ```\n"
).
-spec trim_end(binary()) -> binary().
trim_end(String) ->
string:trim(String, trailing).
-file("src/gleam/string.gleam", 547).
?DOC(
" Removes whitespace on both sides of a `String`.\n"
"\n"
" Whitespace in this function is the set of nonbreakable whitespace\n"
" codepoints, defined as Pattern_White_Space in [Unicode Standard Annex #31][1].\n"
"\n"
" [1]: https://unicode.org/reports/tr31/\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" trim(\" hats \\n\")\n"
" // -> \"hats\"\n"
" ```\n"
).
-spec trim(binary()) -> binary().
trim(String) ->
_pipe = String,
_pipe@1 = trim_start(_pipe),
trim_end(_pipe@1).
-file("src/gleam/string.gleam", 610).
?DOC(
" Splits a non-empty `String` into its first element (head) and rest (tail).\n"
" This lets you pattern match on `String`s exactly as you would with lists.\n"
"\n"
" ## Performance\n"
"\n"
" There is a notable overhead to using this function, so you may not want to\n"
" use it in a tight loop. If you wish to efficiently parse a string you may\n"
" want to use alternatives such as the [splitter package](https://hex.pm/packages/splitter).\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" pop_grapheme(\"gleam\")\n"
" // -> Ok(#(\"g\", \"leam\"))\n"
" ```\n"
"\n"
" ```gleam\n"
" pop_grapheme(\"\")\n"
" // -> Error(Nil)\n"
" ```\n"
).
-spec pop_grapheme(binary()) -> {ok, {binary(), binary()}} | {error, nil}.
pop_grapheme(String) ->
gleam_stdlib:string_pop_grapheme(String).
-file("src/gleam/string.gleam", 234).
?DOC(
" Drops *n* graphemes from the start of a `String`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" drop_start(from: \"The Lone Gunmen\", up_to: 2)\n"
" // -> \"e Lone Gunmen\"\n"
" ```\n"
).
-spec drop_start(binary(), integer()) -> binary().
drop_start(String, Num_graphemes) ->
case Num_graphemes > 0 of
false ->
String;
true ->
case gleam_stdlib:string_pop_grapheme(String) of
{ok, {_, String@1}} ->
drop_start(String@1, Num_graphemes - 1);
{error, nil} ->
String
end
end.
-file("src/gleam/string.gleam", 626).
-spec to_graphemes_loop(binary(), list(binary())) -> list(binary()).
to_graphemes_loop(String, Acc) ->
case gleam_stdlib:string_pop_grapheme(String) of
{ok, {Grapheme, Rest}} ->
to_graphemes_loop(Rest, [Grapheme | Acc]);
{error, _} ->
Acc
end.
-file("src/gleam/string.gleam", 621).
?DOC(
" Converts a `String` to a list of\n"
" [graphemes](https://en.wikipedia.org/wiki/Grapheme).\n"
"\n"
" ```gleam\n"
" to_graphemes(\"abc\")\n"
" // -> [\"a\", \"b\", \"c\"]\n"
" ```\n"
).
-spec to_graphemes(binary()) -> list(binary()).
to_graphemes(String) ->
_pipe = to_graphemes_loop(String, []),
lists:reverse(_pipe).
-file("src/gleam/string.gleam", 319).
?DOC(
" Creates a list of `String`s by splitting a given string on a given substring.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" split(\"home/gleam/desktop/\", on: \"/\")\n"
" // -> [\"home\", \"gleam\", \"desktop\", \"\"]\n"
" ```\n"
).
-spec split(binary(), binary()) -> list(binary()).
split(X, Substring) ->
case Substring of
<<""/utf8>> ->
to_graphemes(X);
_ ->
_pipe = X,
_pipe@1 = gleam_stdlib:identity(_pipe),
_pipe@2 = gleam@string_tree:split(_pipe@1, Substring),
gleam@list:map(_pipe@2, fun unicode:characters_to_binary/1)
end.
-file("src/gleam/string.gleam", 673).
-spec to_utf_codepoints_loop(bitstring(), list(integer())) -> list(integer()).
to_utf_codepoints_loop(Bit_array, Acc) ->
case Bit_array of
<<First/utf8, Rest/binary>> ->
to_utf_codepoints_loop(Rest, [First | Acc]);
_ ->
lists:reverse(Acc)
end.
-file("src/gleam/string.gleam", 668).
-spec do_to_utf_codepoints(binary()) -> list(integer()).
do_to_utf_codepoints(String) ->
to_utf_codepoints_loop(<<String/binary>>, []).
-file("src/gleam/string.gleam", 663).
?DOC(
" Converts a `String` to a `List` of `UtfCodepoint`.\n"
"\n"
" See <https://en.wikipedia.org/wiki/Code_point> and\n"
" <https://en.wikipedia.org/wiki/Unicode#Codespace_and_Code_Points> for an\n"
" explanation on code points.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" \"a\" |> to_utf_codepoints\n"
" // -> [UtfCodepoint(97)]\n"
" ```\n"
"\n"
" ```gleam\n"
" // Semantically the same as:\n"
" // [\"🏳\", \"\", \"\", \"🌈\"] or:\n"
" // [waving_white_flag, variant_selector_16, zero_width_joiner, rainbow]\n"
" \"🏳️‍🌈\" |> to_utf_codepoints\n"
" // -> [\n"
" // UtfCodepoint(127987),\n"
" // UtfCodepoint(65039),\n"
" // UtfCodepoint(8205),\n"
" // UtfCodepoint(127752),\n"
" // ]\n"
" ```\n"
).
-spec to_utf_codepoints(binary()) -> list(integer()).
to_utf_codepoints(String) ->
do_to_utf_codepoints(String).
-file("src/gleam/string.gleam", 713).
?DOC(
" Converts a `List` of `UtfCodepoint`s to a `String`.\n"
"\n"
" See <https://en.wikipedia.org/wiki/Code_point> and\n"
" <https://en.wikipedia.org/wiki/Unicode#Codespace_and_Code_Points> for an\n"
" explanation on code points.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let assert Ok(a) = utf_codepoint(97)\n"
" let assert Ok(b) = utf_codepoint(98)\n"
" let assert Ok(c) = utf_codepoint(99)\n"
" from_utf_codepoints([a, b, c])\n"
" // -> \"abc\"\n"
" ```\n"
).
-spec from_utf_codepoints(list(integer())) -> binary().
from_utf_codepoints(Utf_codepoints) ->
gleam_stdlib:utf_codepoint_list_to_string(Utf_codepoints).
-file("src/gleam/string.gleam", 719).
?DOC(
" Converts an integer to a `UtfCodepoint`.\n"
"\n"
" Returns an `Error` if the integer does not represent a valid UTF codepoint.\n"
).
-spec utf_codepoint(integer()) -> {ok, integer()} | {error, nil}.
utf_codepoint(Value) ->
case Value of
I when I > 1114111 ->
{error, nil};
I@1 when (I@1 >= 55296) andalso (I@1 =< 57343) ->
{error, nil};
I@2 when I@2 < 0 ->
{error, nil};
I@3 ->
{ok, gleam_stdlib:identity(I@3)}
end.
-file("src/gleam/string.gleam", 740).
?DOC(
" Converts an UtfCodepoint to its ordinal code point value.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let assert [utf_codepoint, ..] = to_utf_codepoints(\"💜\")\n"
" utf_codepoint_to_int(utf_codepoint)\n"
" // -> 128156\n"
" ```\n"
).
-spec utf_codepoint_to_int(integer()) -> integer().
utf_codepoint_to_int(Cp) ->
gleam_stdlib:identity(Cp).
-file("src/gleam/string.gleam", 757).
?DOC(
" Converts a `String` into `Option(String)` where an empty `String` becomes\n"
" `None`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" to_option(\"\")\n"
" // -> None\n"
" ```\n"
"\n"
" ```gleam\n"
" to_option(\"hats\")\n"
" // -> Some(\"hats\")\n"
" ```\n"
).
-spec to_option(binary()) -> gleam@option:option(binary()).
to_option(String) ->
case String of
<<""/utf8>> ->
none;
_ ->
{some, String}
end.
-file("src/gleam/string.gleam", 780).
?DOC(
" Returns the first grapheme cluster in a given `String` and wraps it in a\n"
" `Result(String, Nil)`. If the `String` is empty, it returns `Error(Nil)`.\n"
" Otherwise, it returns `Ok(String)`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" first(\"\")\n"
" // -> Error(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" first(\"icecream\")\n"
" // -> Ok(\"i\")\n"
" ```\n"
).
-spec first(binary()) -> {ok, binary()} | {error, nil}.
first(String) ->
case gleam_stdlib:string_pop_grapheme(String) of
{ok, {First, _}} ->
{ok, First};
{error, E} ->
{error, E}
end.
-file("src/gleam/string.gleam", 803).
?DOC(
" Returns the last grapheme cluster in a given `String` and wraps it in a\n"
" `Result(String, Nil)`. If the `String` is empty, it returns `Error(Nil)`.\n"
" Otherwise, it returns `Ok(String)`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" last(\"\")\n"
" // -> Error(Nil)\n"
" ```\n"
"\n"
" ```gleam\n"
" last(\"icecream\")\n"
" // -> Ok(\"m\")\n"
" ```\n"
).
-spec last(binary()) -> {ok, binary()} | {error, nil}.
last(String) ->
case gleam_stdlib:string_pop_grapheme(String) of
{ok, {First, <<""/utf8>>}} ->
{ok, First};
{ok, {_, Rest}} ->
{ok, slice(Rest, -1, 1)};
{error, E} ->
{error, E}
end.
-file("src/gleam/string.gleam", 821).
?DOC(
" Creates a new `String` with the first grapheme in the input `String`\n"
" converted to uppercase and the remaining graphemes to lowercase.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" capitalise(\"mamouna\")\n"
" // -> \"Mamouna\"\n"
" ```\n"
).
-spec capitalise(binary()) -> binary().
capitalise(String) ->
case gleam_stdlib:string_pop_grapheme(String) of
{ok, {First, Rest}} ->
append(string:uppercase(First), string:lowercase(Rest));
{error, _} ->
<<""/utf8>>
end.
-file("src/gleam/string.gleam", 830).
?DOC(" Returns a `String` representation of a term in Gleam syntax.\n").
-spec inspect(any()) -> binary().
inspect(Term) ->
_pipe = gleam_stdlib:inspect(Term),
unicode:characters_to_binary(_pipe).
-file("src/gleam/string.gleam", 853).
?DOC(
" Returns the number of bytes in a `String`.\n"
"\n"
" This function runs in constant time on Erlang and in linear time on\n"
" JavaScript.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" byte_size(\"🏳️‍⚧️🏳️‍🌈👩🏾‍❤️‍👨🏻\")\n"
" // -> 58\n"
" ```\n"
).
-spec byte_size(binary()) -> integer().
byte_size(String) ->
erlang:byte_size(String).

View file

@ -0,0 +1,207 @@
-module(gleam@string_tree).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/gleam/string_tree.gleam").
-export([append_tree/2, prepend_tree/2, from_strings/1, new/0, concat/1, from_string/1, prepend/2, append/2, to_string/1, byte_size/1, join/2, lowercase/1, uppercase/1, reverse/1, split/2, replace/3, is_equal/2, is_empty/1]).
-export_type([string_tree/0, direction/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type string_tree() :: any().
-type direction() :: all.
-file("src/gleam/string_tree.gleam", 61).
?DOC(
" Appends some `StringTree` onto the end of another.\n"
"\n"
" Runs in constant time.\n"
).
-spec append_tree(string_tree(), string_tree()) -> string_tree().
append_tree(Tree, Suffix) ->
gleam_stdlib:iodata_append(Tree, Suffix).
-file("src/gleam/string_tree.gleam", 48).
?DOC(
" Prepends some `StringTree` onto the start of another.\n"
"\n"
" Runs in constant time.\n"
).
-spec prepend_tree(string_tree(), string_tree()) -> string_tree().
prepend_tree(Tree, Prefix) ->
gleam_stdlib:iodata_append(Prefix, Tree).
-file("src/gleam/string_tree.gleam", 69).
?DOC(
" Converts a list of strings into a `StringTree`.\n"
"\n"
" Runs in constant time.\n"
).
-spec from_strings(list(binary())) -> string_tree().
from_strings(Strings) ->
gleam_stdlib:identity(Strings).
-file("src/gleam/string_tree.gleam", 24).
?DOC(
" Create an empty `StringTree`. Useful as the start of a pipe chaining many\n"
" trees together.\n"
).
-spec new() -> string_tree().
new() ->
gleam_stdlib:identity([]).
-file("src/gleam/string_tree.gleam", 77).
?DOC(
" Joins a list of trees into a single tree.\n"
"\n"
" Runs in constant time.\n"
).
-spec concat(list(string_tree())) -> string_tree().
concat(Trees) ->
gleam_stdlib:identity(Trees).
-file("src/gleam/string_tree.gleam", 85).
?DOC(
" Converts a string into a `StringTree`.\n"
"\n"
" Runs in constant time.\n"
).
-spec from_string(binary()) -> string_tree().
from_string(String) ->
gleam_stdlib:identity(String).
-file("src/gleam/string_tree.gleam", 32).
?DOC(
" Prepends a `String` onto the start of some `StringTree`.\n"
"\n"
" Runs in constant time.\n"
).
-spec prepend(string_tree(), binary()) -> string_tree().
prepend(Tree, Prefix) ->
gleam_stdlib:iodata_append(gleam_stdlib:identity(Prefix), Tree).
-file("src/gleam/string_tree.gleam", 40).
?DOC(
" Appends a `String` onto the end of some `StringTree`.\n"
"\n"
" Runs in constant time.\n"
).
-spec append(string_tree(), binary()) -> string_tree().
append(Tree, Second) ->
gleam_stdlib:iodata_append(Tree, gleam_stdlib:identity(Second)).
-file("src/gleam/string_tree.gleam", 94).
?DOC(
" Turns a `StringTree` into a `String`\n"
"\n"
" This function is implemented natively by the virtual machine and is highly\n"
" optimised.\n"
).
-spec to_string(string_tree()) -> binary().
to_string(Tree) ->
unicode:characters_to_binary(Tree).
-file("src/gleam/string_tree.gleam", 100).
?DOC(" Returns the size of the `StringTree` in bytes.\n").
-spec byte_size(string_tree()) -> integer().
byte_size(Tree) ->
erlang:iolist_size(Tree).
-file("src/gleam/string_tree.gleam", 104).
?DOC(" Joins the given trees into a new tree separated with the given string.\n").
-spec join(list(string_tree()), binary()) -> string_tree().
join(Trees, Sep) ->
_pipe = Trees,
_pipe@1 = gleam@list:intersperse(_pipe, gleam_stdlib:identity(Sep)),
gleam_stdlib:identity(_pipe@1).
-file("src/gleam/string_tree.gleam", 115).
?DOC(
" Converts a `StringTree` to a new one where the contents have been\n"
" lowercased.\n"
).
-spec lowercase(string_tree()) -> string_tree().
lowercase(Tree) ->
string:lowercase(Tree).
-file("src/gleam/string_tree.gleam", 122).
?DOC(
" Converts a `StringTree` to a new one where the contents have been\n"
" uppercased.\n"
).
-spec uppercase(string_tree()) -> string_tree().
uppercase(Tree) ->
string:uppercase(Tree).
-file("src/gleam/string_tree.gleam", 127).
?DOC(" Converts a `StringTree` to a new one with the contents reversed.\n").
-spec reverse(string_tree()) -> string_tree().
reverse(Tree) ->
string:reverse(Tree).
-file("src/gleam/string_tree.gleam", 145).
?DOC(" Splits a `StringTree` on a given pattern into a list of trees.\n").
-spec split(string_tree(), binary()) -> list(string_tree()).
split(Tree, Pattern) ->
string:split(Tree, Pattern, all).
-file("src/gleam/string_tree.gleam", 156).
?DOC(" Replaces all instances of a pattern with a given string substitute.\n").
-spec replace(string_tree(), binary(), binary()) -> string_tree().
replace(Tree, Pattern, Substitute) ->
gleam_stdlib:string_replace(Tree, Pattern, Substitute).
-file("src/gleam/string_tree.gleam", 182).
?DOC(
" Compares two string trees to determine if they have the same textual\n"
" content.\n"
"\n"
" Comparing two string trees using the `==` operator may return `False` even\n"
" if they have the same content as they may have been build in different ways,\n"
" so using this function is often preferred.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_strings([\"a\", \"b\"]) == from_string(\"ab\")\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" is_equal(from_strings([\"a\", \"b\"]), from_string(\"ab\"))\n"
" // -> True\n"
" ```\n"
).
-spec is_equal(string_tree(), string_tree()) -> boolean().
is_equal(A, B) ->
string:equal(A, B).
-file("src/gleam/string_tree.gleam", 206).
?DOC(
" Inspects a `StringTree` to determine if it is equivalent to an empty string.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" from_string(\"ok\") |> is_empty\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" from_string(\"\") |> is_empty\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" from_strings([]) |> is_empty\n"
" // -> True\n"
" ```\n"
).
-spec is_empty(string_tree()) -> boolean().
is_empty(Tree) ->
string:is_empty(Tree).

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,529 @@
-module(gleam_stdlib).
-export([
map_get/2, iodata_append/2, identity/1, parse_int/1, parse_float/1,
less_than/2, string_pop_grapheme/1, string_pop_codeunit/1,
string_starts_with/2, wrap_list/1, string_ends_with/2, string_pad/4,
uri_parse/1, bit_array_slice/3, percent_encode/1, percent_decode/1,
base_decode64/1, parse_query/1, bit_array_concat/1,
bit_array_base64_encode/2, tuple_get/2, classify_dynamic/1, print/1,
println/1, print_error/1, println_error/1, inspect/1, float_to_string/1,
int_from_base_string/2, utf_codepoint_list_to_string/1, contains_string/2,
crop_string/2, base16_encode/1, base16_decode/1, string_replace/3, slice/3,
bit_array_to_int_and_size/1, bit_array_pad_to_bytes/1, index/2, list/5,
dict/1, int/1, float/1, bit_array/1, is_null/1
]).
%% Taken from OTP's uri_string module
-define(DEC2HEX(X),
if ((X) >= 0) andalso ((X) =< 9) -> (X) + $0;
((X) >= 10) andalso ((X) =< 15) -> (X) + $A - 10
end).
%% Taken from OTP's uri_string module
-define(HEX2DEC(X),
if ((X) >= $0) andalso ((X) =< $9) -> (X) - $0;
((X) >= $A) andalso ((X) =< $F) -> (X) - $A + 10;
((X) >= $a) andalso ((X) =< $f) -> (X) - $a + 10
end).
-define(is_lowercase_char(X),
(X > 96 andalso X < 123)).
-define(is_underscore_char(X),
(X == 95)).
-define(is_digit_char(X),
(X > 47 andalso X < 58)).
-define(is_ascii_character(X),
(erlang:is_integer(X) andalso X >= 32 andalso X =< 126)).
uppercase(X) -> X - 32.
map_get(Map, Key) ->
case maps:find(Key, Map) of
error -> {error, nil};
OkFound -> OkFound
end.
iodata_append(Iodata, String) -> [Iodata, String].
identity(X) -> X.
classify_dynamic(nil) -> <<"Nil">>;
classify_dynamic(null) -> <<"Nil">>;
classify_dynamic(undefined) -> <<"Nil">>;
classify_dynamic(X) when is_boolean(X) -> <<"Bool">>;
classify_dynamic(X) when is_atom(X) -> <<"Atom">>;
classify_dynamic(X) when is_binary(X) -> <<"String">>;
classify_dynamic(X) when is_bitstring(X) -> <<"BitArray">>;
classify_dynamic(X) when is_integer(X) -> <<"Int">>;
classify_dynamic(X) when is_float(X) -> <<"Float">>;
classify_dynamic(X) when is_list(X) -> <<"List">>;
classify_dynamic(X) when is_map(X) -> <<"Dict">>;
classify_dynamic(X) when is_tuple(X) -> <<"Array">>;
classify_dynamic(X) when is_reference(X) -> <<"Reference">>;
classify_dynamic(X) when is_pid(X) -> <<"Pid">>;
classify_dynamic(X) when is_port(X) -> <<"Port">>;
classify_dynamic(X) when
is_function(X, 0) orelse is_function(X, 1) orelse is_function(X, 2) orelse
is_function(X, 3) orelse is_function(X, 4) orelse is_function(X, 5) orelse
is_function(X, 6) orelse is_function(X, 7) orelse is_function(X, 8) orelse
is_function(X, 9) orelse is_function(X, 10) orelse is_function(X, 11) orelse
is_function(X, 12) -> <<"Function">>;
classify_dynamic(_) -> <<"Unknown">>.
tuple_get(_tup, Index) when Index < 0 -> {error, nil};
tuple_get(Data, Index) when Index >= tuple_size(Data) -> {error, nil};
tuple_get(Data, Index) -> {ok, element(Index + 1, Data)}.
int_from_base_string(String, Base) ->
case catch binary_to_integer(String, Base) of
Int when is_integer(Int) -> {ok, Int};
_ -> {error, nil}
end.
parse_int(String) ->
case catch binary_to_integer(String) of
Int when is_integer(Int) -> {ok, Int};
_ -> {error, nil}
end.
parse_float(String) ->
case catch binary_to_float(String) of
Float when is_float(Float) -> {ok, Float};
_ -> {error, nil}
end.
less_than(Lhs, Rhs) ->
Lhs < Rhs.
string_starts_with(_, <<>>) -> true;
string_starts_with(String, Prefix) when byte_size(Prefix) > byte_size(String) -> false;
string_starts_with(String, Prefix) ->
PrefixSize = byte_size(Prefix),
Prefix == binary_part(String, 0, PrefixSize).
string_ends_with(_, <<>>) -> true;
string_ends_with(String, Suffix) when byte_size(Suffix) > byte_size(String) -> false;
string_ends_with(String, Suffix) ->
SuffixSize = byte_size(Suffix),
Suffix == binary_part(String, byte_size(String) - SuffixSize, SuffixSize).
string_pad(String, Length, Dir, PadString) ->
Chars = string:pad(String, Length, Dir, binary_to_list(PadString)),
case unicode:characters_to_binary(Chars) of
Bin when is_binary(Bin) -> Bin;
Error -> erlang:error({gleam_error, {string_invalid_utf8, Error}})
end.
string_pop_grapheme(String) ->
case string:next_grapheme(String) of
[ Next | Rest ] when is_binary(Rest) ->
{ok, {unicode:characters_to_binary([Next]), Rest}};
[ Next | Rest ] ->
{ok, {unicode:characters_to_binary([Next]), unicode:characters_to_binary(Rest)}};
_ -> {error, nil}
end.
string_pop_codeunit(<<Cp/integer, Rest/binary>>) -> {Cp, Rest};
string_pop_codeunit(Binary) -> {0, Binary}.
bit_array_pad_to_bytes(Bin) ->
case erlang:bit_size(Bin) rem 8 of
0 -> Bin;
TrailingBits ->
PaddingBits = 8 - TrailingBits,
<<Bin/bits, 0:PaddingBits>>
end.
bit_array_concat(BitArrays) ->
list_to_bitstring(BitArrays).
-if(?OTP_RELEASE >= 26).
bit_array_base64_encode(Bin, Padding) ->
PaddedBin = bit_array_pad_to_bytes(Bin),
base64:encode(PaddedBin, #{padding => Padding}).
-else.
bit_array_base64_encode(_Bin, _Padding) ->
erlang:error(<<"Erlang OTP/26 or higher is required to use base64:encode">>).
-endif.
bit_array_slice(Bin, Pos, Len) ->
try {ok, binary:part(Bin, Pos, Len)}
catch error:badarg -> {error, nil}
end.
base_decode64(S) ->
try {ok, base64:decode(S)}
catch error:_ -> {error, nil}
end.
wrap_list(X) when is_list(X) -> X;
wrap_list(X) -> [X].
parse_query(Query) ->
case uri_string:dissect_query(Query) of
{error, _, _} -> {error, nil};
Pairs ->
Pairs1 = lists:map(fun
({K, true}) -> {K, <<"">>};
(Pair) -> Pair
end, Pairs),
{ok, Pairs1}
end.
percent_encode(B) -> percent_encode(B, <<>>).
percent_encode(<<>>, Acc) ->
Acc;
percent_encode(<<H,T/binary>>, Acc) ->
case percent_ok(H) of
true ->
percent_encode(T, <<Acc/binary,H>>);
false ->
<<A:4,B:4>> = <<H>>,
percent_encode(T, <<Acc/binary,$%,(?DEC2HEX(A)),(?DEC2HEX(B))>>)
end.
percent_decode(Cs) -> percent_decode(Cs, <<>>).
percent_decode(<<$%, C0, C1, Cs/binary>>, Acc) ->
case is_hex_digit(C0) andalso is_hex_digit(C1) of
true ->
B = ?HEX2DEC(C0)*16+?HEX2DEC(C1),
percent_decode(Cs, <<Acc/binary, B>>);
false ->
{error, nil}
end;
percent_decode(<<C,Cs/binary>>, Acc) ->
percent_decode(Cs, <<Acc/binary, C>>);
percent_decode(<<>>, Acc) ->
check_utf8(Acc).
percent_ok($!) -> true;
percent_ok($$) -> true;
percent_ok($') -> true;
percent_ok($() -> true;
percent_ok($)) -> true;
percent_ok($*) -> true;
percent_ok($+) -> true;
percent_ok($-) -> true;
percent_ok($.) -> true;
percent_ok($_) -> true;
percent_ok($~) -> true;
percent_ok(C) when $0 =< C, C =< $9 -> true;
percent_ok(C) when $A =< C, C =< $Z -> true;
percent_ok(C) when $a =< C, C =< $z -> true;
percent_ok(_) -> false.
is_hex_digit(C) ->
($0 =< C andalso C =< $9) orelse ($a =< C andalso C =< $f) orelse ($A =< C andalso C =< $F).
check_utf8(Cs) ->
case unicode:characters_to_list(Cs) of
{incomplete, _, _} -> {error, nil};
{error, _, _} -> {error, nil};
_ -> {ok, Cs}
end.
uri_parse(String) ->
case uri_string:parse(String) of
{error, _, _} -> {error, nil};
Uri ->
{ok, {uri,
maps_get_optional(Uri, scheme),
maps_get_optional(Uri, userinfo),
maps_get_optional(Uri, host),
maps_get_optional(Uri, port),
maps_get_or(Uri, path, <<>>),
maps_get_optional(Uri, query),
maps_get_optional(Uri, fragment)
}}
end.
maps_get_optional(Map, Key) ->
try {some, maps:get(Key, Map)}
catch _:_ -> none
end.
maps_get_or(Map, Key, Default) ->
try maps:get(Key, Map)
catch _:_ -> Default
end.
print(String) ->
io:put_chars(String),
nil.
println(String) ->
io:put_chars([String, $\n]),
nil.
print_error(String) ->
io:put_chars(standard_error, String),
nil.
println_error(String) ->
io:put_chars(standard_error, [String, $\n]),
nil.
inspect(true) ->
"True";
inspect(false) ->
"False";
inspect(nil) ->
"Nil";
inspect(Data) when is_map(Data) ->
Fields = [
[<<"#(">>, inspect(Key), <<", ">>, inspect(Value), <<")">>]
|| {Key, Value} <- maps:to_list(Data)
],
["dict.from_list([", lists:join(", ", Fields), "])"];
inspect(Atom) when is_atom(Atom) ->
erlang:element(2, inspect_atom(Atom));
inspect(Any) when is_integer(Any) ->
erlang:integer_to_list(Any);
inspect(Any) when is_float(Any) ->
io_lib_format:fwrite_g(Any);
inspect(Binary) when is_binary(Binary) ->
case inspect_maybe_utf8_string(Binary, <<>>) of
{ok, InspectedUtf8String} -> InspectedUtf8String;
{error, not_a_utf8_string} ->
Segments = [erlang:integer_to_list(X) || <<X>> <= Binary],
["<<", lists:join(", ", Segments), ">>"]
end;
inspect(Bits) when is_bitstring(Bits) ->
inspect_bit_array(Bits);
inspect(List) when is_list(List) ->
case inspect_list(List, true) of
{charlist, _} -> ["charlist.from_string(\"", list_to_binary(List), "\")"];
{proper, Elements} -> ["[", Elements, "]"];
{improper, Elements} -> ["//erl([", Elements, "])"]
end;
inspect(Any) when is_tuple(Any) % Record constructors
andalso is_atom(element(1, Any))
andalso element(1, Any) =/= false
andalso element(1, Any) =/= true
andalso element(1, Any) =/= nil
->
[Atom | ArgsList] = erlang:tuple_to_list(Any),
InspectedArgs = lists:map(fun inspect/1, ArgsList),
case inspect_atom(Atom) of
{gleam_atom, GleamAtom} ->
Args = lists:join(<<", ">>, InspectedArgs),
[GleamAtom, "(", Args, ")"];
{erlang_atom, ErlangAtom} ->
Args = lists:join(<<", ">>, [ErlangAtom | InspectedArgs]),
["#(", Args, ")"]
end;
inspect(Tuple) when is_tuple(Tuple) ->
Elements = lists:map(fun inspect/1, erlang:tuple_to_list(Tuple)),
["#(", lists:join(", ", Elements), ")"];
inspect(Any) when is_function(Any) ->
{arity, Arity} = erlang:fun_info(Any, arity),
ArgsAsciiCodes = lists:seq($a, $a + Arity - 1),
Args = lists:join(<<", ">>,
lists:map(fun(Arg) -> <<Arg>> end, ArgsAsciiCodes)
),
["//fn(", Args, ") { ... }"];
inspect(Any) ->
["//erl(", io_lib:format("~p", [Any]), ")"].
inspect_atom(Atom) ->
Binary = erlang:atom_to_binary(Atom),
case inspect_maybe_gleam_atom(Binary, none, <<>>) of
{ok, Inspected} -> {gleam_atom, Inspected};
{error, _} -> {erlang_atom, ["atom.create_from_string(\"", Binary, "\")"]}
end.
inspect_maybe_gleam_atom(<<>>, none, _) ->
{error, nil};
inspect_maybe_gleam_atom(<<First, _Rest/binary>>, none, _) when ?is_digit_char(First) ->
{error, nil};
inspect_maybe_gleam_atom(<<"_", _Rest/binary>>, none, _) ->
{error, nil};
inspect_maybe_gleam_atom(<<"_">>, _PrevChar, _Acc) ->
{error, nil};
inspect_maybe_gleam_atom(<<"_", _Rest/binary>>, $_, _Acc) ->
{error, nil};
inspect_maybe_gleam_atom(<<First, _Rest/binary>>, _PrevChar, _Acc)
when not (?is_lowercase_char(First) orelse ?is_underscore_char(First) orelse ?is_digit_char(First)) ->
{error, nil};
inspect_maybe_gleam_atom(<<First, Rest/binary>>, none, Acc) ->
inspect_maybe_gleam_atom(Rest, First, <<Acc/binary, (uppercase(First))>>);
inspect_maybe_gleam_atom(<<"_", Rest/binary>>, _PrevChar, Acc) ->
inspect_maybe_gleam_atom(Rest, $_, Acc);
inspect_maybe_gleam_atom(<<First, Rest/binary>>, $_, Acc) ->
inspect_maybe_gleam_atom(Rest, First, <<Acc/binary, (uppercase(First))>>);
inspect_maybe_gleam_atom(<<First, Rest/binary>>, _PrevChar, Acc) ->
inspect_maybe_gleam_atom(Rest, First, <<Acc/binary, First>>);
inspect_maybe_gleam_atom(<<>>, _PrevChar, Acc) ->
{ok, Acc};
inspect_maybe_gleam_atom(A, B, C) ->
erlang:display({A, B, C}),
throw({gleam_error, A, B, C}).
inspect_list([], _) ->
{proper, []};
inspect_list([First], true) when ?is_ascii_character(First) ->
{charlist, nil};
inspect_list([First], _) ->
{proper, [inspect(First)]};
inspect_list([First | Rest], ValidCharlist) when is_list(Rest) ->
StillValidCharlist = ValidCharlist andalso ?is_ascii_character(First),
{Kind, Inspected} = inspect_list(Rest, StillValidCharlist),
{Kind, [inspect(First), <<", ">> | Inspected]};
inspect_list([First | ImproperTail], _) ->
{improper, [inspect(First), <<" | ">>, inspect(ImproperTail)]}.
inspect_bit_array(Bits) ->
Text = inspect_bit_array(Bits, <<"<<">>),
<<Text/binary, ">>">>.
inspect_bit_array(<<>>, Acc) ->
Acc;
inspect_bit_array(<<X, Rest/bitstring>>, Acc) ->
inspect_bit_array(Rest, append_segment(Acc, erlang:integer_to_binary(X)));
inspect_bit_array(Rest, Acc) ->
Size = bit_size(Rest),
<<X:Size>> = Rest,
X1 = erlang:integer_to_binary(X),
Size1 = erlang:integer_to_binary(Size),
Segment = <<X1/binary, ":size(", Size1/binary, ")">>,
inspect_bit_array(<<>>, append_segment(Acc, Segment)).
bit_array_to_int_and_size(A) ->
Size = bit_size(A),
<<A1:Size>> = A,
{A1, Size}.
append_segment(<<"<<">>, Segment) ->
<<"<<", Segment/binary>>;
append_segment(Acc, Segment) ->
<<Acc/binary, ", ", Segment/binary>>.
inspect_maybe_utf8_string(Binary, Acc) ->
case Binary of
<<>> -> {ok, <<$", Acc/binary, $">>};
<<First/utf8, Rest/binary>> ->
Escaped = case First of
$" -> <<$\\, $">>;
$\\ -> <<$\\, $\\>>;
$\r -> <<$\\, $r>>;
$\n -> <<$\\, $n>>;
$\t -> <<$\\, $t>>;
$\f -> <<$\\, $f>>;
X when X > 126, X < 160 -> convert_to_u(X);
X when X < 32 -> convert_to_u(X);
Other -> <<Other/utf8>>
end,
inspect_maybe_utf8_string(Rest, <<Acc/binary, Escaped/binary>>);
_ -> {error, not_a_utf8_string}
end.
convert_to_u(Code) ->
list_to_binary(io_lib:format("\\u{~4.16.0B}", [Code])).
float_to_string(Float) when is_float(Float) ->
erlang:iolist_to_binary(io_lib_format:fwrite_g(Float)).
utf_codepoint_list_to_string(List) ->
case unicode:characters_to_binary(List) of
{error, _} -> erlang:error({gleam_error, {string_invalid_utf8, List}});
Binary -> Binary
end.
crop_string(String, Prefix) ->
case string:find(String, Prefix) of
nomatch -> String;
New -> New
end.
contains_string(String, Substring) ->
is_bitstring(string:find(String, Substring)).
base16_encode(Bin) ->
PaddedBin = bit_array_pad_to_bytes(Bin),
binary:encode_hex(PaddedBin).
base16_decode(String) ->
try
{ok, binary:decode_hex(String)}
catch
_:_ -> {error, nil}
end.
string_replace(String, Pattern, Replacement) ->
string:replace(String, Pattern, Replacement, all).
slice(String, Index, Length) ->
case string:slice(String, Index, Length) of
X when is_binary(X) -> X;
X when is_list(X) -> unicode:characters_to_binary(X)
end.
index([X | _], 0) ->
{ok, {some, X}};
index([_, X | _], 1) ->
{ok, {some, X}};
index([_, _, X | _], 2) ->
{ok, {some, X}};
index([_, _, _, X | _], 3) ->
{ok, {some, X}};
index([_, _, _, _, X | _], 4) ->
{ok, {some, X}};
index([_, _, _, _, _, X | _], 5) ->
{ok, {some, X}};
index([_, _, _, _, _, _, X | _], 6) ->
{ok, {some, X}};
index([_, _, _, _, _, _, _, X | _], 7) ->
{ok, {some, X}};
index(Tuple, Index) when is_tuple(Tuple) andalso is_integer(Index) ->
{ok, try
{some, element(Index + 1, Tuple)}
catch _:_ ->
none
end};
index(Map, Key) when is_map(Map) ->
{ok, try
{some, maps:get(Key, Map)}
catch _:_ ->
none
end};
index(_, Index) when is_integer(Index) ->
{error, <<"Indexable">>};
index(_, _) ->
{error, <<"Dict">>}.
list(T, A, B, C, D) when is_tuple(T) ->
list(tuple_to_list(T), A, B, C, D);
list([], _, _, _, Acc) ->
{lists:reverse(Acc), []};
list([X | Xs], Decode, PushPath, Index, Acc) ->
{Out, Errors} = Decode(X),
case Errors of
[] -> list(Xs, Decode, PushPath, Index + 1, [Out | Acc]);
_ -> PushPath({[], Errors}, integer_to_binary(Index))
end;
list(Unexpected, _, _, _, []) ->
Found = gleam@dynamic:classify(Unexpected),
Error = {decode_error, <<"List"/utf8>>, Found, []},
{[], [Error]};
list(_, _, _, _, Acc) ->
{lists:reverse(Acc), []}.
dict(#{} = Data) -> {ok, Data};
dict(_) -> {error, nil}.
int(I) when is_integer(I) -> {ok, I};
int(_) -> {error, 0}.
float(F) when is_float(F) -> {ok, F};
float(_) -> {error, 0.0}.
bit_array(B) when is_bitstring(B) -> {ok, B};
bit_array(_) -> {error, <<>>}.
is_null(X) ->
X =:= undefined orelse X =:= null orelse X =:= nil.

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more