├── bin └── alpaca ├── src ├── alpaca_boot.erl ├── apcshell.app.src ├── apcshell_app.erl ├── apcshell_sup.erl ├── alpaca_shell.erl └── alpaca_ast.hrl ├── rebar.lock ├── rebar.config ├── .gitignore ├── CHANGELOG └── README.md /bin/alpaca: -------------------------------------------------------------------------------- 1 | ERL_LIBS=./_build/default/lib:./_build/default/plugins erl -user alpaca_boot 2 | -------------------------------------------------------------------------------- /src/alpaca_boot.erl: -------------------------------------------------------------------------------- 1 | -module(alpaca_boot). 2 | 3 | -export([start/0]). 4 | 5 | start() -> user_drv:start(['tty_sl -c -e',{alpaca_shell,start,[]}]). -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | [{<<"epo_runtime">>, 2 | {git,"git://github.com/brigadier/epo_runtime.git", 3 | {ref,"a3e50e7cebb526f833757e867bbe914c1da7baa3"}}, 4 | 0}]. 5 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | 3 | {deps, [{epo_runtime, {git, "git://github.com/brigadier/epo_runtime.git", 4 | {tag, "0.3"}}} 5 | ]}. 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .rebar3 2 | _* 3 | .eunit 4 | *.o 5 | *.beam 6 | *.plt 7 | *.swp 8 | *.swo 9 | .erlang.cookie 10 | ebin 11 | log 12 | erl_crash.dump 13 | .rebar 14 | logs 15 | _build 16 | .idea 17 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | * v0.0.2 2 | - functions and values can now be defined and evaluated 3 | - the master branch version of Alpaca is now used by default 4 | - display better error on type unify mismatch and duplicate definitions -------------------------------------------------------------------------------- /src/apcshell.app.src: -------------------------------------------------------------------------------- 1 | {application, apcshell, 2 | [{description, "A shell for Alpaca"}, 3 | {vsn, "0.0.3"}, 4 | {registered, []}, 5 | {mod, { apcshell_app, []}}, 6 | {applications, 7 | [kernel, 8 | stdlib 9 | ]}, 10 | {env,[]}, 11 | {modules, []}, 12 | 13 | {maintainers, []}, 14 | {licenses, []}, 15 | {links, []} 16 | ]}. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## WARNING! this is currently being built against a development version of Alpaca so it is almost certainly broken for most use cases. It will be operational and tagged against the next version of Alpaca (0.2.8) when that is released (hopefully June 2017) 2 | 3 | Alpaca Shell 4 | ===== 5 | 6 | An experimental interactive shell for Alpaca 7 | https://github.com/alpaca-lang/alpaca, an ML-inspired language that 8 | runs on beam. 9 | 10 | Build 11 | ----- 12 | 13 | $ rebar3 compile 14 | 15 | Run 16 | ----- 17 | 18 | $ bin/alpaca 19 | -------------------------------------------------------------------------------- /src/apcshell_app.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %% @doc apcshell public API 3 | %% @end 4 | %%%------------------------------------------------------------------- 5 | 6 | -module(apcshell_app). 7 | 8 | -behaviour(application). 9 | 10 | %% Application callbacks 11 | -export([start/2, stop/1]). 12 | 13 | %%==================================================================== 14 | %% API 15 | %%==================================================================== 16 | 17 | start(_StartType, _StartArgs) -> 18 | apcshell_sup:start_link(). 19 | 20 | %%-------------------------------------------------------------------- 21 | stop(_State) -> 22 | ok. 23 | 24 | %%==================================================================== 25 | %% Internal functions 26 | %%==================================================================== 27 | -------------------------------------------------------------------------------- /src/apcshell_sup.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %% @doc apcshell top level supervisor. 3 | %% @end 4 | %%%------------------------------------------------------------------- 5 | 6 | -module(apcshell_sup). 7 | 8 | -behaviour(supervisor). 9 | 10 | %% API 11 | -export([start_link/0]). 12 | 13 | %% Supervisor callbacks 14 | -export([init/1]). 15 | 16 | -define(SERVER, ?MODULE). 17 | 18 | %%==================================================================== 19 | %% API functions 20 | %%==================================================================== 21 | 22 | start_link() -> 23 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 24 | 25 | %%==================================================================== 26 | %% Supervisor callbacks 27 | %%==================================================================== 28 | 29 | %% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules} 30 | init([]) -> 31 | {ok, { {one_for_all, 0, 1}, []} }. 32 | 33 | %%==================================================================== 34 | %% Internal functions 35 | %%==================================================================== 36 | -------------------------------------------------------------------------------- /src/alpaca_shell.erl: -------------------------------------------------------------------------------- 1 | -module(alpaca_shell). 2 | 3 | -export([start/0, server/0]). 4 | 5 | -include_lib("src/alpaca_ast.hrl"). 6 | 7 | -ifdef(TEST). 8 | -include_lib("eunit/include/eunit.hrl"). 9 | -endif. 10 | 11 | -record(repl_state, {bindings = [], 12 | functions = [], 13 | types = [], 14 | shell_id = undefined}). 15 | 16 | %% Entrypoints 17 | 18 | start() -> 19 | spawn(fun () -> server() end). 20 | 21 | server() -> 22 | ensure_alpaca(), 23 | %% Trap exits 24 | process_flag(trap_exit, true), 25 | %% Print welcome banner 26 | io:put_chars(" == \x1b[34m Alpaca Shell 0.0.3 \x1b[0m== \n\n" 27 | " (hint: exit with ctrl-c, run expression by terminating with" 28 | " ';;' or an empty line)\n\n"), 29 | %% Generate a unique identifier for this shell 30 | ShellId = binary_to_list(base64:encode(crypto:strong_rand_bytes(12))), 31 | %% Enter main server loop 32 | 33 | server_loop(#repl_state{shell_id=ShellId}). 34 | 35 | 36 | %% RESULT PRINTING 37 | 38 | %^ Format the result 39 | format_result(Result) when is_binary(Result) -> 40 | io_lib:format("\"~s\"", [Result]); 41 | 42 | format_result(Result) -> 43 | io_lib:format("~s", [format_value(Result)]). 44 | 45 | format_type({unbound, T, _}) -> 46 | case T of 47 | t0 -> "'a"; 48 | t1 -> "'b"; 49 | t2 -> "'c"; 50 | t3 -> "'d"; 51 | t4 -> "'e"; 52 | t5 -> "'f"; 53 | t6 -> "'g"; 54 | t7 -> "'h"; 55 | t8 -> "'i"; 56 | _ -> "'?" 57 | end; 58 | format_type(T) when is_atom(T) -> 59 | atom_to_list(T); 60 | format_type({t_list, T}) -> 61 | io_lib:format("list ~s", [format_type(T)]); 62 | format_type({t_tuple, Types}) -> 63 | TypeNames = lists:map(fun format_type/1, Types), 64 | TypeString = string:join(TypeNames, ", "), 65 | Output = io_lib:format("(~s)", [TypeString]), 66 | lists:flatten(Output); 67 | format_type({t_record, Members, _}) -> 68 | MemberList = lists:map(fun({t_record_member, Name, T}) -> 69 | atom_to_list(Name) ++ " : " ++ format_type(T) 70 | end, Members), 71 | MemberString = string:join(MemberList, ", "), 72 | "{" ++ MemberString ++ "}"; 73 | format_type(Other) -> 74 | io_lib:format("~p", [Other]). 75 | 76 | output_result(Result, {t_arrow, Args, Return}) -> 77 | ListifiedArgs = lists:map(fun format_type/1, Args), 78 | ArgList = string:join(ListifiedArgs, " -> "), 79 | 80 | print_result(io_lib:format(" :: ~s -> ~s", [ArgList, format_type(Return)])); 81 | 82 | output_result(Result, Type) -> 83 | print_result(io_lib:format("~s :: ~s", [format_result(Result), format_type(Type)])). 84 | 85 | print_result(Text) -> 86 | io:format("\x1b[32m -- ~s\x1b[0m\n\n", [Text]). 87 | 88 | %% EXPRESION EXECUTION 89 | 90 | %%run_bind(Funs, Bin) -> 91 | %% code:load_binary(alpaca_user_shell, Funs, Bin), 92 | %% ERROR PRINTING 93 | 94 | format_error(Err) -> 95 | alpaca_error_format:fmt({error, Err}, "en_US"). 96 | 97 | output_error(Text) -> 98 | io:format("\x1b[31m -- ~s\x1b[0m\n\n", [Text]). 99 | 100 | %% COMPILING 101 | compile_typed(Module, Beams, State = #repl_state{shell_id=ShellId}) -> 102 | %% Write module code to temporary file 103 | TempFile = "/tmp/shell_" ++ ShellId ++ ".alp", 104 | file:write_file(TempFile, Module, [write, sync]), 105 | %% Wait until it has definitely written (sync) 106 | (fun WaitSync() -> 107 | case file:read_file_info(TempFile) of 108 | {ok, _} -> ok; 109 | enoent -> timer:sleep(5), WaitSync() 110 | end 111 | end 112 | )(), 113 | 114 | case alpaca:compile({files, [TempFile | Beams]}) of 115 | {ok, Mods} -> 116 | [{compiled_module, Name, FN, B}] = Mods, 117 | {module, Mod} = code:load_binary(Name, FN, B), 118 | ModTypes = proplists:get_value( 119 | alpaca_typeinfo, Mod:module_info(attributes)), 120 | {ok, {Mod, ModTypes}}; 121 | {error, _} = Err -> Err 122 | end. 123 | 124 | 125 | %% VALUE FORMATTING (injects Erlang values into Alpaca source) 126 | %% TODO - it might be wiser to generate tokens rather than raw strings 127 | 128 | format_value(Record = #{'__struct__' := record}) -> 129 | NoStruct = maps:filter(fun(K, _) -> K =/= '__struct__' end, Record), 130 | RecordParts = lists:map(fun({K, V}) -> 131 | atom_to_list(K) ++ " = " ++ format_value(V) 132 | end, maps:to_list(NoStruct)), 133 | "{" ++ string:join(RecordParts, ", ") ++ "}"; 134 | format_value(V) when is_atom(V) -> 135 | io_lib:format(":~w", [V]); 136 | format_value(V) -> io_lib:format("~w", [V]). 137 | 138 | render_bind({Name, Type, Result}) -> 139 | lists:flatten(io_lib:format("let ~s = ~s in \n", [Name, format_value(Result)])). 140 | 141 | render_fun(Body) -> 142 | Body ++ "\n". 143 | 144 | build_module(State = #repl_state{bindings = Bindings, functions = Funs}) -> 145 | FunsList = lists:map(fun render_fun/1, Funs), 146 | FunsString = string:join(FunsList, "\n"), 147 | BindingsList = lists:map(fun render_bind/1, Bindings), 148 | BindingsString = string:join(BindingsList, "\n"), 149 | "module user_shell\n" 150 | "export main/1\n\n" ++ 151 | FunsString ++ 152 | "let main () = \n " ++ BindingsString. 153 | 154 | find_main_type([]) -> 155 | {error, main_not_found}; 156 | find_main_type([Type | Rest] = Types) when is_list(Types) -> 157 | case Type of 158 | #alpaca_binding{ 159 | type={t_arrow, [t_unit], ReturnType}, 160 | name={symbol, _, "main"}} -> 161 | ReturnType; 162 | 163 | #alpaca_binding{ 164 | type={t_arrow, [t_unit], ReturnType}, 165 | name={'Symbol', #{name := <<"main">>}}} -> 166 | ReturnType; 167 | 168 | _ -> find_main_type(Rest) 169 | end; 170 | 171 | find_main_type(#alpaca_module{functions=FunDefs}) -> 172 | find_main_type(FunDefs). 173 | 174 | collect_beams(Module) -> 175 | %% Collect .beam files for any referenced dependencies 176 | {user_shell, DepModules} = alpaca:list_dependencies(Module), 177 | ModRefs = lists:map( 178 | fun(M) -> "alpaca_" ++ atom_to_list(M) ++ ".beam" end, 179 | DepModules), 180 | 181 | lists:filtermap(fun(M) -> case code:where_is_file(M) of 182 | non_existing -> false; 183 | Path -> {true, Path} 184 | end 185 | end, 186 | ModRefs). 187 | 188 | run_expression(Expr, State) -> 189 | %% Construct a fake module and inject the entered expression 190 | %% into a fake function main/1 so we can call it from Erlang 191 | %% Compile the module 192 | Module = build_module(State) ++ "\n " ++ Expr ++ "\n\n", 193 | Beams = collect_beams(Module), 194 | 195 | case compile_typed(Module, Beams, State) of 196 | {ok, {Mod, Types}} -> 197 | MainType = find_main_type(Types), 198 | %% Execute the main function and return both the value and the 199 | %% inferred type. 200 | try Mod:main({}) of 201 | Val -> {ok, {Val, MainType}} 202 | catch 203 | Other -> Other 204 | end; 205 | {error, _} = Err -> Err; 206 | Other -> Other 207 | end. 208 | 209 | run_expression(Expr) -> 210 | run_expression(Expr, #repl_state{}). 211 | 212 | handle_expression(Expr, State) -> 213 | case run_expression(Expr, State) of 214 | {ok, {Val, MainType}} -> output_result(Val, MainType); 215 | {error, Err} -> output_error(format_error(Err)) 216 | end, 217 | State. 218 | 219 | handle_fundef(Expr, State = #repl_state{functions = Functions}, {Symbol, #{name := Name}}) -> 220 | NewFuns = [Expr | Functions], 221 | StateWithFun = State#repl_state{functions=NewFuns}, 222 | Module = build_module(StateWithFun) ++ " :ok", 223 | Beams = collect_beams(Module), 224 | case compile_typed(Module, Beams, State) of 225 | {ok, _} -> 226 | State#repl_state{functions = NewFuns}; 227 | {error, Err} -> {error, Err, State}; 228 | Other -> {error, Other, State} 229 | end. 230 | 231 | handle_bind(Expr, 232 | State = #repl_state{bindings = Bindings}, 233 | {'Symbol', #{name := Name}}) -> 234 | 235 | BindingExpr = Expr ++ " in " ++ binary_to_list(Name) ++ "\n\n", 236 | Module = build_module(State) ++ BindingExpr, 237 | Beams = collect_beams(Module), 238 | case compile_typed(Module, Beams, State) of 239 | {ok, {Mod, Types}} -> 240 | MainType = find_main_type(Types), 241 | %% Value bind - execute the expression and store the result 242 | try Mod:main({}) of 243 | Res -> Bindings_ = Bindings ++ [{Name, MainType, Res}], 244 | State#repl_state{bindings = Bindings_} 245 | catch 246 | error:Err -> {error, Err} 247 | end 248 | end. 249 | 250 | %% INPUT PARSING 251 | 252 | % Try and identify what sort of input the user entered. 253 | parse_input("") -> {empty, ""}; 254 | parse_input(Input) -> 255 | case alpaca_scanner:scan(Input) of 256 | {ok, Toks, NumLines} -> 257 | case alpaca_ast_gen:parse(Toks) of 258 | 259 | {ok, #alpaca_binding{ 260 | name=Name, 261 | bound_expr=#alpaca_fun{arity=Arity}, 262 | body=undefined}} -> 263 | case Arity of 264 | 0 -> {bind_value, Name}; 265 | _ -> {bind_fun, Name} 266 | end; 267 | 268 | {ok, #alpaca_binding{name=Name, body=undefined}} -> {bind_value, Name}; 269 | 270 | %% TODO - this is nasty 271 | {ok, {error, non_literal_value, Name, _}} -> 272 | {bind_value, Name}; 273 | 274 | {ok, Other} -> {expression, Other}; 275 | {error, _} = Err -> Err 276 | end; 277 | {error, Err, _} -> {error, Err} 278 | end. 279 | % Strip ;; and newline terminators 280 | strip_terminator(Line) -> 281 | L = re:replace(Line, ";;\n$", "", [{return, list}]), 282 | re:replace(L, "\n$", "", [{return, list}]). 283 | 284 | % Termination happens if a line is empty or terminates with ;; 285 | line_terminates(Line) -> 286 | (re:run(Line, ";;\n$") /= nomatch) or (Line == "\n"). 287 | 288 | % Read input until terminating condition found 289 | read_input(Prompt, Lines) -> 290 | Line = io:get_line(Prompt), 291 | Lines_ = Lines ++ strip_terminator(Line), 292 | case line_terminates(Line) of 293 | true -> Lines_; 294 | false -> read_input(" \x1b[33m... \x1b[0m", Lines_) 295 | end. 296 | 297 | read_input(Prompt) -> 298 | read_input(Prompt, []). 299 | 300 | %% MAIN LOOP 301 | 302 | server_loop(State) -> 303 | % Collect input - supporting functions or types currently 304 | Input = read_input(" \x1b[33m " ++ [955] ++ "\x1b[0m "), 305 | State_ = case parse_input(Input) of 306 | {empty, _} -> io:format(" -- Nothing entered\n\n"), State; 307 | {expression, _} -> handle_expression(Input, State); 308 | {bind_value, Name} -> handle_bind(Input, State, Name); 309 | {bind_fun, Name} -> handle_fundef(Input, State, Name); 310 | {error, Err} -> output_error(format_error(Err)), State 311 | end, 312 | server_loop(State_). 313 | 314 | ensure_alpaca() -> 315 | %% Locate Alpaca compiler 316 | AlpacaHome = os:getenv("ALPACA_ROOT", "/usr/local/opt/alpaca/ebin"), 317 | code:add_path(AlpacaHome), 318 | AlpacaModules = 319 | [alpaca, alpaca_ast, alpaca_ast_gen, alpaca_codegen, 320 | alpaca_compiled_po, alpaca_error_format, alpaca_exhaustiveness, 321 | alpaca_parser, alpaca_scan, alpaca_scanner, alpaca_typer], 322 | ok = code:ensure_modules_loaded(AlpacaModules). 323 | 324 | -ifdef(TEST). 325 | 326 | input_type_test_() -> 327 | [?_assertMatch({bind_fun, {symbol, _, "myfun"}}, parse_input("let myfun f = 10")), 328 | ?_assertMatch({bind_value, {symbol, _, "myval"}}, parse_input("let myval = 42")), 329 | ?_assertMatch({expression, _}, parse_input("100")), 330 | ?_assertMatch({expression, _}, parse_input("let f = 10 in f")), 331 | ?_assertMatch({expression, _}, parse_input("let f x = x in f"))]. 332 | 333 | expression_type_test_() -> 334 | [?_assertMatch({ok, {42, t_int}}, run_expression("42")), 335 | ?_assertMatch({ok, {<<"hello">>, t_string}}, run_expression("\"hello\"")), 336 | ?_assertMatch({ok, {_, {t_arrow, [t_int], t_int}}}, 337 | run_expression("let f x = x + 1 in f"))]. 338 | 339 | error_test_() -> 340 | [?_assertMatch({error, {cannot_unify, _, _, t_int, t_string}}, 341 | run_expression("\"hello\" + 42")), 342 | ?_assertMatch({error, {1, alpaca_parser, ["syntax error before: ", "break"]}}, 343 | parse_input("let a b c;;"))]. 344 | 345 | value_bind_test() -> 346 | State = handle_bind("let num = 42", #repl_state{}, {symbol, 1, "num"}), 347 | ?assertMatch(#repl_state{bindings = [{"num", t_int, 42}]}, State), 348 | ?assertMatch({ok, {42, t_int}}, run_expression("num", State)). 349 | 350 | value_expression_bind_test() -> 351 | State = handle_bind("let num = 24 + 24", #repl_state{}, {symbol, 1, "num"}), 352 | ?assertMatch(#repl_state{bindings = [{"num", t_int, 48}]}, State), 353 | ?assertMatch({ok, {48, t_int}}, run_expression("num", State)). 354 | 355 | fun_bind_test() -> 356 | State = handle_fundef("let sqr x = x * x", #repl_state{}, {symbol, 1, "sqr"}), 357 | ?assertMatch(#repl_state{functions = ["let sqr x = x * x"]}, State). 358 | 359 | -endif. 360 | -------------------------------------------------------------------------------- /src/alpaca_ast.hrl: -------------------------------------------------------------------------------- 1 | %%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- 2 | %%% ex: ft=erlang ts=4 sw=4 et 3 | %%% 4 | %%% Copyright 2016 Jeremy Pierre 5 | %%% 6 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 7 | %%% you may not use this file except in compliance with the License. 8 | %%% You may obtain a copy of the License at 9 | %%% 10 | %%% http://www.apache.org/licenses/LICENSE-2.0 11 | %%% 12 | %%% Unless required by applicable law or agreed to in writing, software 13 | %%% distributed under the License is distributed on an "AS IS" BASIS, 14 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | %%% See the License for the specific language governing permissions and 16 | %%% limitations under the License. 17 | 18 | %%% ## Type-Tracking Data Types 19 | %%% 20 | %%% These are all of the specs the typer uses to track Alpaca types. 21 | 22 | -type typ_name() :: atom(). 23 | 24 | -type qvar() :: {qvar, typ_name()}. 25 | -type tvar() :: {unbound, typ_name(), integer()} 26 | | {link, typ()}. 27 | %% list of parameter types, return type: 28 | -type t_arrow() :: {t_arrow, list(typ()), typ()}. 29 | 30 | -record(adt, {name=undefined :: undefined|string(), 31 | module=undefined :: atom(), 32 | vars=[] :: list({string(), typ()}), 33 | members=[] :: list(typ())}). 34 | -type t_adt() :: #adt{}. 35 | 36 | -type t_adt_constructor() :: {t_adt_cons, string()}. 37 | 38 | %% Processes that are spawned with functions that are not receivers are not 39 | %% allowed to be sent messages. 40 | -type t_pid() :: {t_pid, typ()}. 41 | 42 | -type t_receiver() :: {t_receiver, typ(), typ()}. 43 | 44 | -type t_list() :: {t_list, typ()}. 45 | 46 | -type t_map() :: {t_map, typ(), typ()}. 47 | 48 | -type t_tuple() :: {t_tuple, list(typ())}. 49 | 50 | %% pattern, optional guard, result. Currently I'm doing nothing with 51 | %% present guards. 52 | %% TODO: the guards don't need to be part of the type here. Their 53 | %% only role in typing is to constrain the pattern's typing. 54 | -type t_clause() :: {t_clause, typ(), t_arrow()|undefined, typ()}. 55 | 56 | %%% `t_rec` is a special type that denotes an infinitely recursive function. 57 | %%% Since all functions here are considered recursive, the return type for 58 | %%% any function must begin as `t_rec`. `t_rec` unifies with anything else by 59 | %%% becoming that other thing and as such should be in its own reference cell. 60 | -type t_const() :: t_rec 61 | | t_int 62 | | t_float 63 | | t_atom 64 | | t_bool 65 | | t_string 66 | | t_chars 67 | | t_unit. 68 | 69 | -type typ() :: undefined 70 | | qvar() 71 | | tvar() 72 | | t_arrow() 73 | | t_adt() 74 | | t_adt_constructor() 75 | | t_const() 76 | | t_binary 77 | | t_list() 78 | | t_map() 79 | | t_record() 80 | | t_tuple() 81 | | t_clause() 82 | | t_pid() 83 | | t_receiver() 84 | | alpaca_typer:t_cell(). % a reference cell for a type. 85 | 86 | %%% ## ALPACA AST Nodes 87 | 88 | -record(alpaca_comment, { 89 | multi_line=false :: boolean(), 90 | line=0 :: integer(), 91 | text="" :: string()}). 92 | -type alpaca_comment() :: #alpaca_comment{}. 93 | 94 | 95 | -type alpaca_symbol() :: {symbol, integer(), string()}. 96 | %% Reference to a symbol in a different module. Arity can be 'none' 97 | %% if the user wishes to default to the first exported version of the 98 | %% reference. 99 | -record(alpaca_far_ref, { 100 | line=0 :: integer(), 101 | module=undefined :: atom(), 102 | name="" :: string(), 103 | arity=none :: none | integer()}). 104 | -type alpaca_far_ref() :: #alpaca_far_ref{}. 105 | 106 | -type alpaca_unit() :: {unit, integer()}. 107 | -type alpaca_int() :: {'Int', #{line := integer(), val := integer()}}. 108 | -type alpaca_float() :: {float, integer(), float()}. 109 | -type alpaca_number() :: alpaca_int()|alpaca_float(). 110 | -type alpaca_bool() :: {bool, integer(), boolean()}. 111 | -type alpaca_atom() :: {atom, integer(), atom()}. 112 | 113 | -type alpaca_error() :: {raise_error, 114 | integer(), 115 | throw|error|exit, 116 | alpaca_value_expression()}. 117 | 118 | %%% The variable _, meaning "don't care": 119 | -type alpaca_any() :: {any, integer()}. 120 | 121 | -type alpaca_string() :: {string, integer(), string()}. 122 | 123 | -type alpaca_const() :: alpaca_unit() 124 | | alpaca_any() 125 | | alpaca_number() 126 | | alpaca_bool() 127 | | alpaca_atom() 128 | | alpaca_string() 129 | . 130 | 131 | %%% ### Binaries 132 | 133 | -record(alpaca_binary, {line=0 :: integer(), 134 | segments=[] :: list(alpaca_bits())}). 135 | -type alpaca_binary() :: #alpaca_binary{}. 136 | 137 | -type alpaca_bits_type() :: int | float | binary | utf8. 138 | 139 | -record(alpaca_bits, {line=0 :: integer(), 140 | %% Used to signal whether or not the bitstring is simply 141 | %% using default size and unit values. If it is *not* 142 | %% and the `type` is `binary` *and* the bitstring is the 143 | %% last segment in a binary, it's size must be set to 144 | %% `'all'` with unit 8 to capture all remaining bits. 145 | %% This is in keeping with how Erlang compiles to Core 146 | %% Erlang. 147 | default_sizes=true :: boolean(), 148 | value={symbol, 0, ""} :: alpaca_symbol()|alpaca_number()|alpaca_string(), 149 | size=8 :: non_neg_integer()|all, 150 | unit=1 :: non_neg_integer(), 151 | type=int :: alpaca_bits_type(), 152 | sign=unsigned :: signed | unsigned, 153 | endian=big :: big | little | native}). 154 | -type alpaca_bits() :: #alpaca_bits{}. 155 | 156 | %%% ### AST Nodes For Types 157 | %%% 158 | %%% AST nodes that describe the basic included types and constructs for 159 | %%% defining and instantiating ADTs (type constructors). 160 | 161 | -type alpaca_base_type() :: t_atom 162 | | t_int 163 | | t_float 164 | | t_string 165 | | t_pid 166 | | t_bool 167 | | t_chars 168 | | t_unit. 169 | 170 | -type alpaca_type_name() :: {type_name, integer(), string()}. 171 | -type alpaca_type_var() :: {type_var, integer(), string()}. 172 | 173 | -record(alpaca_type_tuple, { 174 | members=[] :: list(alpaca_base_type() 175 | | alpaca_type_var() 176 | | alpaca_poly_type()) 177 | }). 178 | -type alpaca_type_tuple() :: #alpaca_type_tuple{}. 179 | 180 | -type alpaca_list_type() :: {t_list, alpaca_base_type()|alpaca_poly_type()}. 181 | 182 | -type alpaca_map_type() :: {t_map, 183 | alpaca_base_type()|alpaca_poly_type(), 184 | alpaca_base_type()|alpaca_poly_type()}. 185 | 186 | -type alpaca_pid_type() :: {t_list, alpaca_base_type()|alpaca_poly_type()}. 187 | 188 | -type alpaca_poly_type() :: alpaca_type() 189 | | alpaca_type_tuple() 190 | | alpaca_list_type() 191 | | alpaca_map_type() 192 | | alpaca_pid_type(). 193 | 194 | %%% ### Record Type Tracking 195 | %%% 196 | %%% These will do double-duty for both defining record types for ADTs 197 | %%% as well as to type records as they occur. 198 | -record(t_record_member, { 199 | name=undefined :: atom(), 200 | type=undefined :: typ()}). 201 | -type t_record_member() :: #t_record_member{}. 202 | 203 | -record(t_record, { 204 | is_pattern=false :: boolean(), 205 | members=[] :: list(t_record_member()), 206 | row_var=undefined :: typ()}). 207 | 208 | -type t_record() :: #t_record{}. 209 | 210 | %%% ADT Type Tracking 211 | 212 | -record(type_constructor, { 213 | line=0 :: integer(), 214 | module=undefined :: atom(), 215 | name="" :: string() 216 | }). 217 | -type alpaca_constructor_name() :: #type_constructor{}. 218 | 219 | -record(alpaca_constructor, { 220 | type=undefined :: typ() | alpaca_type(), 221 | name=#type_constructor{} :: alpaca_constructor_name(), 222 | arg=none :: none 223 | | alpaca_base_type() 224 | | alpaca_type_var() 225 | | alpaca_type() 226 | | alpaca_type_tuple() 227 | }). 228 | -type alpaca_constructor() :: #alpaca_constructor{}. 229 | 230 | -type alpaca_types() :: alpaca_type() 231 | | alpaca_type_tuple() 232 | | alpaca_base_type() 233 | | alpaca_list_type() 234 | | alpaca_map_type() 235 | | alpaca_pid_type(). 236 | 237 | -record(alpaca_type, { 238 | line=0 :: integer(), 239 | module=undefined :: atom(), 240 | name={type_name, -1, ""} :: alpaca_type_name(), 241 | vars=[] :: list(alpaca_type_var() 242 | | {alpaca_type_var(), typ()}), 243 | members=[] :: list(alpaca_constructor() 244 | | alpaca_type_var() 245 | | alpaca_types()) 246 | }). 247 | -type alpaca_type() :: #alpaca_type{}. 248 | 249 | -record(alpaca_type_apply, { 250 | type=undefined :: typ(), 251 | name=#type_constructor{} :: alpaca_constructor_name(), 252 | arg=none :: none | alpaca_expression()}). 253 | -type alpaca_type_apply() :: #alpaca_type_apply{}. 254 | 255 | %%% ### Lists 256 | 257 | -record(alpaca_cons, {type=undefined :: typ(), 258 | line=0 :: integer(), 259 | head=undefined :: undefined|alpaca_expression(), 260 | tail={nil, 0} :: alpaca_expression() 261 | }). 262 | 263 | -type alpaca_cons() :: #alpaca_cons{}. 264 | -type alpaca_nil() :: {nil, integer()}. 265 | -type alpaca_list() :: alpaca_cons() | alpaca_nil(). 266 | 267 | %%% ### Maps 268 | %%% 269 | %%% For both map literals and map patterns 270 | 271 | -record(alpaca_map_pair, {type=undefined :: typ(), 272 | line=0 :: integer(), 273 | is_pattern=false :: boolean(), 274 | key=undefined :: alpaca_value_expression(), 275 | val=undefined :: alpaca_value_expression()}). 276 | -type alpaca_map_pair() :: #alpaca_map_pair{}. 277 | 278 | %% The `structure` field tracks what we're actually using the map for. 279 | %% The code generation stage will add a member to the compiled map that 280 | %% indicates what the purpose of the map is so that pattern matches can 281 | %% be correct, e.g. we don't want the order of maps and records to matter 282 | %% in a pattern match because then compilation details are a concern for 283 | %% a user. 284 | -record(alpaca_map, {type=undefined :: typ(), 285 | line=0 :: integer(), 286 | is_pattern=false :: boolean(), 287 | structure=map :: map | record, 288 | pairs=[] :: list(alpaca_map_pair())}). 289 | -type alpaca_map() :: #alpaca_map{}. 290 | 291 | -record(alpaca_map_add, {type=undefined :: typ(), 292 | line=0 :: integer(), 293 | to_add=#alpaca_map_pair{} :: alpaca_map_pair(), 294 | existing=#alpaca_map{} :: alpaca_value_expression()}). 295 | -type alpaca_map_add() :: #alpaca_map_add{}. 296 | 297 | %%% ### Tuples 298 | 299 | -record(alpaca_tuple, {type=undefined :: typ(), 300 | arity=0 :: integer(), 301 | values=[] :: list(alpaca_expression()) 302 | }). 303 | -type alpaca_tuple() :: #alpaca_tuple{}. 304 | 305 | %%% ### Record AST Nodes 306 | 307 | -record(alpaca_record_member, { 308 | line=-1 :: integer(), 309 | name=undefined :: atom(), 310 | type=undefined :: typ(), 311 | val={symbol, -1, ""} :: alpaca_value_expression()}). 312 | -type alpaca_record_member() :: #alpaca_record_member{}. 313 | 314 | -record(alpaca_record, {arity=0 :: integer(), 315 | line=0 :: integer(), 316 | is_pattern=false :: boolean(), 317 | members=[] :: list(alpaca_record_member())}). 318 | -type alpaca_record() :: #alpaca_record{}. 319 | 320 | -record(alpaca_record_transform, { 321 | line=-1 :: integer(), 322 | additions=[] :: list(alpaca_record_member()), 323 | existing :: alpaca_value_expression()}). 324 | -type alpaca_record_transform() :: #alpaca_record_transform{}. 325 | 326 | %%% Pattern Matching 327 | 328 | -type type_check() :: is_integer 329 | | is_float 330 | | is_atom 331 | | is_bool 332 | | is_list 333 | | is_string 334 | | is_chars 335 | | is_binary. 336 | 337 | %% TODO: revisit this in alpaca_typer.erl as well as scanning and parsing: 338 | -record(alpaca_type_check, {type=undefined :: undefined|type_check(), 339 | line=0 :: integer(), 340 | expr=undefined :: undefined|alpaca_symbol()}). 341 | -type alpaca_type_check() :: #alpaca_type_check{}. 342 | 343 | -record(alpaca_clause, {type=undefined :: typ(), 344 | line=0 :: integer(), 345 | pattern={symbol, 0, "_"} :: alpaca_expression(), 346 | guards=[] :: list(alpaca_expression()), 347 | result={symbol, 0, "_"} :: alpaca_expression() 348 | }). 349 | -type alpaca_clause() :: #alpaca_clause{}. 350 | 351 | -record(alpaca_match, {type=undefined :: typ(), 352 | line=0 :: integer(), 353 | match_expr={symbol, 0, "_"} :: alpaca_expression(), 354 | clauses=[#alpaca_clause{}] :: nonempty_list(alpaca_clause()) 355 | }). 356 | -type alpaca_match() :: #alpaca_match{}. 357 | 358 | %%% ### Erlang FFI 359 | %%% 360 | %%% A call to an Erlang function via the Foreign Function Interface. 361 | %%% Only the result of these calls is typed. 362 | -record(alpaca_ffi, {type=undefined :: typ(), 363 | module={atom, 0, ""} :: alpaca_atom(), 364 | function_name=undefined :: undefined|alpaca_atom(), 365 | args={nil, 0} :: alpaca_list(), 366 | clauses=[] :: list(alpaca_clause()) 367 | }). 368 | -type alpaca_ffi() :: #alpaca_ffi{}. 369 | 370 | %%% ### Processes 371 | 372 | -record(alpaca_spawn, {type=undefined :: typ(), 373 | line=0 :: integer(), 374 | module=undefined :: atom(), 375 | from_module=undefined :: atom(), 376 | function={symbol, 0, ""} :: alpaca_symbol(), 377 | args=[] :: list(alpaca_expression())}). 378 | -type alpaca_spawn() :: #alpaca_spawn{}. 379 | 380 | -record(alpaca_send, {type=undefined :: typ(), 381 | line=0 :: integer(), 382 | message=undefined :: undefined|alpaca_value_expression(), 383 | pid=undefined :: undefined|alpaca_expression()}). 384 | -type alpaca_send() :: #alpaca_send{}. 385 | 386 | -record(alpaca_receive, {type=undefined :: typ(), 387 | line=0 :: integer(), 388 | clauses=[#alpaca_clause{}] :: nonempty_list(alpaca_clause()), 389 | timeout=infinity :: infinity | integer(), 390 | timeout_action=undefined :: undefined 391 | | alpaca_value_expression()}). 392 | -type alpaca_receive() :: #alpaca_receive{}. 393 | 394 | %%% ### Module Building Blocks 395 | 396 | -record(alpaca_test, {type=undefined :: typ(), 397 | line=0 :: integer(), 398 | name={string, 0, ""} :: alpaca_string(), 399 | expression={unit, 0} :: alpaca_expression()}). 400 | -type alpaca_test() :: #alpaca_test{}. 401 | 402 | %%% Expressions that result in values: 403 | -type alpaca_value_expression() :: alpaca_const() 404 | | alpaca_symbol() 405 | | alpaca_far_ref() 406 | | alpaca_list() 407 | | alpaca_binary() 408 | | alpaca_map() 409 | | alpaca_map_add() 410 | | alpaca_record() 411 | | alpaca_record_transform() 412 | | alpaca_tuple() 413 | | alpaca_apply() 414 | | alpaca_type_apply() 415 | | alpaca_match() 416 | | alpaca_receive() 417 | | alpaca_clause() 418 | | alpaca_fun() 419 | | alpaca_spawn() 420 | | alpaca_send() 421 | | alpaca_ffi(). 422 | 423 | -type alpaca_expression() :: alpaca_comment() 424 | | alpaca_value_expression() 425 | | alpaca_binding() 426 | | alpaca_type_check() 427 | | alpaca_binding() 428 | | alpaca_type_import() 429 | | alpaca_type_export() 430 | | alpaca_error(). 431 | 432 | %% When calling BIFs like erlang:'+' it seems core erlang doesn't want 433 | %% the arity specified as part of the function name. alpaca_bif_name() 434 | %% is a way to indicate what the ALPACA function name is and the corresponding 435 | %% actual Erlang BIF. Making the distinction between the ALPACA and Erlang 436 | %% name to support something like '+' for integers and '+.' for floats. 437 | -type alpaca_bif_name() :: 438 | { bif 439 | , AlpacaFun::atom() 440 | , Line::integer() 441 | , Module::atom() 442 | , ErlangFun::atom() 443 | }. 444 | 445 | %%% A function application can occur in one of 4 ways: 446 | %%% 447 | %%% - an Erlang BIF 448 | %%% - intra-module, a function defined in the module it's being called 449 | %%% within or one in scope from a let binding 450 | %%% - inter-module (a "call" in core erlang), calling a function defined 451 | %%% in a different module 452 | %%% - a function bound to a variable 453 | %%% 454 | %%% The distinction is particularly important between the first and third 455 | %%% since core erlang wants the arity specified in the first case but _not_ 456 | %%% in the third. 457 | 458 | -record(alpaca_apply, {type=undefined :: typ(), 459 | line=0 :: integer(), 460 | expr=undefined :: undefined 461 | | {alpaca_symbol(), integer()} 462 | | {atom(), alpaca_symbol(), integer()} 463 | | alpaca_symbol() 464 | | alpaca_bif_name() 465 | | alpaca_expression(), 466 | args=[] :: list(alpaca_expression()) 467 | }). 468 | -type alpaca_apply() :: #alpaca_apply{}. 469 | 470 | -record(alpaca_fun_version, { 471 | line=0 :: integer(), 472 | args=[] :: list(alpaca_value_expression()), 473 | guards=[] :: list(alpaca_expression()), 474 | body=undefined :: undefined|alpaca_expression() 475 | }). 476 | 477 | %% The name field in an #alpaca_fun{} is there for the typer's convenience. 478 | %% When typing an #alpaca_binding{}, the typer inserts the bound name into the 479 | %% function to enable "let rec" behaviour. We could relax this later to allow 480 | %% for non-recursive let behaviour but I can't think of a good reason to go for 481 | %% that at the immediate moment. 482 | -record(alpaca_fun, { 483 | line=0 :: integer(), 484 | type=undefined :: typ(), 485 | arity=0 :: integer(), 486 | name=undefined :: undefined | string(), 487 | versions=[] :: list(#alpaca_fun_version{}) 488 | }). 489 | -type alpaca_fun() :: #alpaca_fun{}. 490 | 491 | %% `body` remains `undefined` for top-level expressions and otherwise for 492 | %% things like function and variable bindings within a top-level function. 493 | -record(alpaca_binding, { 494 | line=0 :: integer(), 495 | name=undefined :: undefined | alpaca_symbol(), 496 | type=undefined :: typ(), 497 | bound_expr=undefined :: undefined | alpaca_expression(), 498 | body=undefined :: undefined | alpaca_expression() 499 | }). 500 | -type alpaca_binding() :: #alpaca_binding{}. 501 | 502 | -record(alpaca_type_import, {module=undefined :: atom(), 503 | type=undefined :: string()}). 504 | -type alpaca_type_import() :: #alpaca_type_import{}. 505 | 506 | -record(alpaca_type_export, {line=0 :: integer(), 507 | names=[] :: list(string())}). 508 | -type alpaca_type_export() :: #alpaca_type_export{}. 509 | 510 | %% rename_map is a map from generated function and variable names to their 511 | %% original names. 512 | -record(alpaca_module, { 513 | name=no_module :: atom(), 514 | filename=undefined :: string() | undefined, 515 | rename_map=maps:new() :: map(), 516 | function_exports=[] :: list({string(), integer()}|string()), 517 | function_imports=[] :: list({string(), {atom(), integer()}|string()}), 518 | types=[] :: list(alpaca_type()), 519 | type_imports=[] :: list(alpaca_type_import()), 520 | type_exports=[] :: list(string()), 521 | functions=[] :: list(alpaca_binding()), 522 | tests=[] :: list(alpaca_test()), 523 | precompiled=false :: boolean(), 524 | hash=undefined :: binary() | undefined 525 | }). 526 | -type alpaca_module() :: #alpaca_module{}. --------------------------------------------------------------------------------