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) 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 { <> |> collect_segments([]) |> cerl_binary |> BinaryExpr } fn collect_segments(bs: BitArray, result: List(BitStr)) -> List(BitStr) { case bs { <<>> -> list.reverse(result) <> -> { 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 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 /// 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 } @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 /// = argument /// body /// ``` /// where you can use a pattern line `p_int("Num")` by its variable /// `c_int("Num") 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 } @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 }) }