Packages

Generate Core Erlang from Gleam (wraps the Erlang cerl compiler module)

Current section

Files

Jump to
gen_core_erlang src gen_core_erlang.gleam
Raw

src/gen_core_erlang.gleam

import gleam/erlang/atom.{type Atom}
import gleam/list.{map}
/// Outermost Core Erlang term that can be passed to `compile:forms(mod, [from_core])`.
pub type ModuleDef
pub type ModuleAttribute
pub type Definition =
#(FunctionDecl, Function)
pub type Clause
/// Type that wraps the various `c_erl()` subtypes into an expression.
/// These expressions are used as function bodies, in case clauses after `->`.
pub opaque type Expression {
ApplyExpr(expression: CErl)
AtomExpr(expression: CErl)
BinaryExpr(expression: CErl)
CaseExpr(expression: CErl)
DoExpr(expression: CErl)
FloatExpr(expression: CErl)
FunctionExpr(expression: CErl)
IntExpr(expression: CErl)
LetExpr(expression: CErl)
ListExpr(expression: CErl)
ReceiveExpr(expression: CErl)
TupleExpr(expression: CErl)
VarExpr(expression: CErl)
}
/// Type that wraps the various `c_erl()` subtypes into a pattern.
/// These patterns are used as function arguments, in case clauses before `->`,
pub opaque type Pattern {
AtomPattern(pattern: CErl)
FloatPattern(pattern: CErl)
IntPattern(pattern: CErl)
ListPattern(pattern: CErl)
NilPattern(pattern: CErl)
TuplePattern(pattern: CErl)
VarPattern(pattern: CErl)
}
/// Type that wraps name and arity, such as `loop/1`.
pub type FunctionDecl
// Type that wraps a function declaration and its implementation.
pub opaque type Function {
Function(expression: CErl)
}
// For when you need a function as an expression.
pub fn f2e(f: Function) -> Expression {
FunctionExpr(f.expression)
}
// the generic c_erl() type; exposing it would destroy type safety.
type CErl
// Call a non-exported function. As in, a local call in Erlang without the `module:` prefix
pub fn c_apply(f_def: FunctionDecl, arguments: List(Expression)) -> Expression {
cerl_apply(f_def, unwrap_expressions(arguments))
|> ApplyExpr
}
@external(erlang, "cerl", "c_apply")
fn cerl_apply(f_def: FunctionDecl, arguments: List(CErl)) -> CErl
/// Call a function, but not by name. Most likely, the function has been assigned
/// to a variable
/// ```gleam
/// c_let([p_var("Fun")], f2e(c_fun(...)), c_call_anonymous(c_var("Fun", [])))
/// ```
pub fn c_apply_anonymous(
function: Expression,
arguments: List(Expression),
) -> Expression {
cerl_apply2(function.expression, unwrap_expressions(arguments))
|> ReceiveExpr
}
@external(erlang, "cerl", "c_apply")
fn cerl_apply2(f_def: CErl, arguments: List(CErl)) -> CErl
/// Create an expression for the atom that has string representation `name`
pub fn c_atom(name: String) -> Expression {
name
|> to_atom
|> AtomExpr
}
/// Create a pattern for the atom that has string representation `name`
pub fn p_atom(name: String) -> Pattern {
name
|> to_atom
|> AtomPattern
}
fn to_atom(name: String) -> CErl {
name
|> atom.create_from_string
|> cerl_atom
}
@external(erlang, "cerl", "c_atom")
fn cerl_atom(atom: Atom) -> CErl
/// Create an Erlang binary for a Gleam string. Currently, there is no
/// way to generate other binaries
pub fn c_binary(string: String) -> Expression {
<<string:utf8>>
|> collect_segments([])
|> cerl_binary
|> BinaryExpr
}
fn collect_segments(bs: BitArray, result: List(BitStr)) -> List(BitStr) {
case bs {
<<>> -> list.reverse(result)
<<b:8, tail:bytes>> -> {
collect_segments(tail, [
cerl_bitstr(
cerl_int(b),
cerl_int(8),
cerl_int(1),
to_atom("integer"),
make_list([c_atom("unsigned"), c_atom("big")]).expression,
),
..result
])
}
_ -> panic as "Unexpected tail in collect_segment"
}
}
@external(erlang, "cerl", "c_binary")
fn cerl_binary(segments: List(BitStr)) -> CErl
@external(erlang, "cerl", "c_bitstr")
fn cerl_bitstr(
value: CErl,
size: CErl,
unit: CErl,
type_: CErl,
flags: CErl,
) -> BitStr
type BitStr
/// Call an exported function
pub fn c_call(
module: String,
name: String,
arguments: List(Expression),
) -> Expression {
cerl_call(to_atom(module), to_atom(name), unwrap_expressions(arguments))
|> ReceiveExpr
}
@external(erlang, "cerl", "c_call")
fn cerl_call(module: CErl, name: CErl, arguments: List(CErl)) -> CErl
pub fn c_case(expr: Expression, clauses: List(Clause)) -> Expression {
cerl_case(expr.expression, clauses)
|> CaseExpr
}
@external(erlang, "cerl", "c_case")
fn cerl_case(expr: CErl, clauses: List(Clause)) -> CErl
/// A single clause for a `c_case`. Although you can pass a list of patterns,
/// it seems `expr` always returns one value
pub fn c_clause(patterns: List(Pattern), expr: Expression) -> Clause {
patterns
|> unwrap_patterns
|> cerl_clause(expr.expression)
}
@external(erlang, "cerl", "c_clause")
fn cerl_clause(patterns: List(CErl), expr: CErl) -> Clause
/// Although we use the type `Expression`, you should only pass those allowed in guards.
/// Maybe some day we can enforce this with a type, but that seems very hard
pub fn c_clause_w_guard(
patterns: List(Pattern),
guard: Expression,
expr: Expression,
) -> Clause {
patterns
|> unwrap_patterns
|> cerl_clause_w_guard(guard.expression, expr.expression)
}
@external(erlang, "cerl", "c_clause")
fn cerl_clause_w_guard(patterns: List(CErl), guard: CErl, expr: CErl) -> Clause
/// Concatenate two expressions. As per Erlang, the `tail` argument
/// does not have to be a list. If you want to generate an empty list,
/// use `c_nil`, if you want to generate a list from 1 or more items,
/// use `make_list`
pub fn c_cons(head: Expression, tail: Expression) -> Expression {
cerl_cons(head.expression, tail.expression)
|> ListExpr
}
pub fn p_cons(head: Pattern, tail: Pattern) -> Pattern {
cerl_cons(head.pattern, tail.pattern)
|> ListPattern
}
@external(erlang, "cerl", "c_cons")
fn cerl_cons(head: CErl, tail: CErl) -> CErl
pub fn c_float(value: Float) -> Expression {
value
|> cerl_float
|> FloatExpr
}
pub fn p_float(value: Float) -> Pattern {
value
|> cerl_float
|> FloatPattern
}
@external(erlang, "cerl", "c_float")
fn cerl_float(value: Float) -> CErl
/// Create a function 'name' such as "loop/1". To be used in module definitions
/// (both exports and functions) as well as local function application
pub fn c_fname(name: String, arity: Int) -> FunctionDecl {
name
|> atom.create_from_string
|> cerl_fname(arity)
}
@external(erlang, "cerl", "c_fname")
fn cerl_fname(name: Atom, arity: Int) -> FunctionDecl
/// If you need this as an `Expression`, use `f2e`
pub fn c_fun(arguments: List(Pattern), body: Expression) -> Function {
arguments
|> unwrap_patterns
|> cerl_fun(body.expression)
|> Function
}
@external(erlang, "cerl", "c_fun")
fn cerl_fun(arguments: List(CErl), body: CErl) -> CErl
pub fn c_int(value: Int) -> Expression {
value
|> cerl_int
|> IntExpr
}
pub fn p_int(value: Int) -> Pattern {
value
|> cerl_int
|> IntPattern
}
@external(erlang, "cerl", "c_int")
fn cerl_int(value: Int) -> CErl
/// Equivalent to
/// ```erlang
/// <P1, ..., Pn> = argument
/// body
/// ```
/// where you can use a pattern line `p_int("Num")` by its variable
/// `c_int("Num")
///
/// Note that all Pi need to be `p_var` patterns. If you need other matches,
/// use `c_case` with a single `c_clause`
pub fn c_let(
variables: List(Pattern),
argument: Expression,
body: Expression,
) -> Expression {
variables
|> unwrap_patterns
|> cerl_let(argument.expression, body.expression)
|> LetExpr
}
@external(erlang, "cerl", "c_let")
fn cerl_let(variables: List(CErl), argument: CErl, body: CErl) -> CErl
/// Convenience function, equivalent to
/// ```gleam
/// c_cons(expr_1, .... c_cons(expr_n, c_nil()))
/// ```
pub fn make_list(expressions: List(Expression)) -> Expression {
expressions
|> unwrap_expressions
|> cerl_make_list(None)
|> ListExpr
}
@external(erlang, "cerl", "make_list")
fn cerl_make_list(expressions: List(CErl), none: WeDoNotPassTheTail) -> CErl
type WeDoNotPassTheTail {
None
}
/// Unspecified behaviour. Used for testing this library.
pub fn list_elements(list: Expression) -> List(Expression) {
list.expression
|> cerl_list_elements
|> map(VarExpr)
}
@external(erlang, "cerl", "list_elements")
fn cerl_list_elements(list: CErl) -> List(CErl)
/// There should be a definition for each export. Other definitions are
/// local functions.
/// Note: For attributes, you currnely have to pass an empty list.
pub fn c_module(
name: String,
exports: List(FunctionDecl),
attributes: List(ModuleAttribute),
definitions: List(Definition),
) -> ModuleDef {
definitions
|> map(fn(def) {
let #(decl, fun) = def
#(decl, fun.expression)
})
|> cerl_module(to_atom(name), exports, attributes, _)
}
@external(erlang, "cerl", "c_module")
fn cerl_module(
name: CErl,
exports: List(FunctionDecl),
attributes: List(ModuleAttribute),
definitions: List(#(FunctionDecl, CErl)),
) -> ModuleDef
pub fn c_receive(clauses: List(Clause)) -> Expression {
clauses
|> cerl_receive
|> ReceiveExpr
}
/// Erlang's empty list.
pub fn c_nil() -> Expression {
cerl_nil()
|> ListExpr
}
pub fn p_nil() -> Pattern {
cerl_nil()
|> NilPattern
}
@external(erlang, "cerl", "c_nil")
fn cerl_nil() -> CErl
@external(erlang, "cerl", "c_receive")
fn cerl_receive(clauses: List(Clause)) -> CErl
/// Execute two expression consecutively.
/// Erlang functions always return a result. If you need that result, use `c_let`.
/// If you want to execute more than 2 expressions, use:
/// ```gleam
/// let assert Ok(expr) = list.reduce(expressions, c_seq)
/// ```
pub fn c_seq(arg: Expression, body: Expression) -> Expression {
cerl_seq(arg.expression, body.expression)
|> DoExpr
}
@external(erlang, "cerl", "c_seq")
fn cerl_seq(arg: CErl, body: CErl) -> CErl
pub fn c_var(name: String) -> Expression {
name
|> atom.create_from_string
|> cerl_var
|> VarExpr
}
pub fn p_var(name: String) -> Pattern {
name
|> atom.create_from_string
|> cerl_var
|> VarPattern
}
pub fn c_tuple(values: List(Expression)) -> Expression {
values
|> unwrap_expressions
|> cerl_tuple
|> TupleExpr
}
pub fn p_tuple(values: List(Pattern)) -> Pattern {
values
|> unwrap_patterns
|> cerl_tuple
|> TuplePattern
}
// this can return ither a c_literal() or a c_tuple(). Since we'd wrap c_literal()
// in the same way, in the two functions above, pretend it's a tuple.
@external(erlang, "cerl", "c_tuple")
fn cerl_tuple(values: List(CErl)) -> CErl
@external(erlang, "cerl", "c_var")
fn cerl_var(name: Atom) -> CErl
fn unwrap_expressions(expressions: List(Expression)) {
expressions
|> map(fn(e: Expression) { e.expression })
}
fn unwrap_patterns(patterns: List(Pattern)) {
patterns
|> map(fn(p: Pattern) { p.pattern })
}