diff --git a/src/Fable.Beam.fsproj b/src/Fable.Beam.fsproj index e08b78c..a4a8697 100644 --- a/src/Fable.Beam.fsproj +++ b/src/Fable.Beam.fsproj @@ -11,6 +11,7 @@ Fable bindings for Erlang and BEAM/OTP + diff --git a/src/otp/Application.fs b/src/otp/Application.fs index 5e72ca9..cd7d9b7 100644 --- a/src/otp/Application.fs +++ b/src/otp/Application.fs @@ -3,7 +3,7 @@ module Fable.Beam.Application open Fable.Core -open Fable.Beam.Erlang +open Fable.Beam // fsharplint:disable MemberNames diff --git a/src/otp/Erlang.fs b/src/otp/Erlang.fs index d2715d3..16b69e7 100644 --- a/src/otp/Erlang.fs +++ b/src/otp/Erlang.fs @@ -1,32 +1,14 @@ /// Type bindings for Erlang BIFs (Built-in Functions) /// See https://www.erlang.org/doc/apps/erts/erlang +[] module Fable.Beam.Erlang open Fable.Core +open Fable.Beam // Note: For selective receive, use Fable.Core.BeamInterop.Erlang.receive<'T> // which is provided by Fable.Core and handled by the compiler. -// ============================================================================ -// Opaque Erlang types -// ============================================================================ - -/// Erlang process identifier. -[] -type Pid = Pid of obj - -/// Erlang reference (from make_ref, monitor, etc.). -[] -type Ref = Ref of obj - -/// Erlang atom. -[] -type Atom = Atom of obj - -/// Erlang timer reference (from send_after, etc.). -[] -type TimerRef = TimerRef of obj - // ============================================================================ // Process management // ============================================================================ diff --git a/src/otp/Ets.fs b/src/otp/Ets.fs index 3c23f4a..2ae05e8 100644 --- a/src/otp/Ets.fs +++ b/src/otp/Ets.fs @@ -3,7 +3,7 @@ module Fable.Beam.Ets open Fable.Core -open Fable.Beam.Erlang +open Fable.Beam // fsharplint:disable MemberNames diff --git a/src/otp/GenServer.fs b/src/otp/GenServer.fs index 91a6df3..51fd4bc 100644 --- a/src/otp/GenServer.fs +++ b/src/otp/GenServer.fs @@ -3,7 +3,7 @@ module Fable.Beam.GenServer open Fable.Core -open Fable.Beam.Erlang +open Fable.Beam // fsharplint:disable MemberNames diff --git a/src/otp/Io.fs b/src/otp/Io.fs index d852d40..219dc27 100644 --- a/src/otp/Io.fs +++ b/src/otp/Io.fs @@ -3,7 +3,7 @@ module Fable.Beam.Io open Fable.Core -open Fable.Beam.Erlang +open Fable.Beam // fsharplint:disable MemberNames diff --git a/src/otp/Logger.fs b/src/otp/Logger.fs index 4a5fc3f..e1f7b5b 100644 --- a/src/otp/Logger.fs +++ b/src/otp/Logger.fs @@ -3,7 +3,7 @@ module Fable.Beam.Logger open Fable.Core -open Fable.Beam.Erlang +open Fable.Beam // fsharplint:disable MemberNames diff --git a/src/otp/Os.fs b/src/otp/Os.fs index c663aad..da7262c 100644 --- a/src/otp/Os.fs +++ b/src/otp/Os.fs @@ -3,6 +3,7 @@ module Fable.Beam.Os open Fable.Core +open Fable.Beam // fsharplint:disable MemberNames @@ -48,7 +49,7 @@ let version () : int * int * int = nativeOnly /// Returns the current OS system time in the given unit (e.g., second, millisecond). [] -let systemTime (unit: Erlang.Atom) : int64 = nativeOnly +let systemTime (unit: Atom) : int64 = nativeOnly /// Returns the current OS system time in seconds. [] diff --git a/src/otp/Supervisor.fs b/src/otp/Supervisor.fs index bf854d1..f69194f 100644 --- a/src/otp/Supervisor.fs +++ b/src/otp/Supervisor.fs @@ -3,7 +3,7 @@ module Fable.Beam.Supervisor open Fable.Core -open Fable.Beam.Erlang +open Fable.Beam // fsharplint:disable MemberNames diff --git a/src/otp/Timer.fs b/src/otp/Timer.fs index ab81cae..ea2b6f1 100644 --- a/src/otp/Timer.fs +++ b/src/otp/Timer.fs @@ -3,7 +3,7 @@ module Fable.Beam.Timer open Fable.Core -open Fable.Beam.Erlang +open Fable.Beam // fsharplint:disable MemberNames diff --git a/src/otp/Types.fs b/src/otp/Types.fs new file mode 100644 index 0000000..979836c --- /dev/null +++ b/src/otp/Types.fs @@ -0,0 +1,20 @@ +/// Shared opaque types for Erlang/OTP bindings. +namespace Fable.Beam + +open Fable.Core + +/// Erlang process identifier. +[] +type Pid = Pid of obj + +/// Erlang reference (from make_ref, monitor, etc.). +[] +type Ref = Ref of obj + +/// Erlang atom. +[] +type Atom = Atom of obj + +/// Erlang timer reference (from send_after, etc.). +[] +type TimerRef = TimerRef of obj diff --git a/test/TestErlang.fs b/test/TestErlang.fs index cde6013..34cd77a 100644 --- a/test/TestErlang.fs +++ b/test/TestErlang.fs @@ -5,7 +5,7 @@ open Fable.Beam.Testing #if FABLE_COMPILER open Fable.Core open Fable.Core.BeamInterop -open Fable.Beam.Erlang +open Fable.Beam type RecvMsg = | [] Ping @@ -15,8 +15,8 @@ type RecvMsg = [] let ``test self returns a pid`` () = #if FABLE_COMPILER - let pid = self () - let isAlive = isProcessAlive pid + let pid = Erlang.self () + let isAlive = Erlang.isProcessAlive pid isAlive |> equal true #else () @@ -25,9 +25,9 @@ let ``test self returns a pid`` () = [] let ``test makeRef returns unique references`` () = #if FABLE_COMPILER - let ref1 = makeRef () - let ref2 = makeRef () - exactEquals ref1 ref2 |> equal false + let ref1 = Erlang.makeRef () + let ref2 = Erlang.makeRef () + Erlang.exactEquals ref1 ref2 |> equal false #else () #endif @@ -35,8 +35,8 @@ let ``test makeRef returns unique references`` () = [] let ``test exactEquals on same ref`` () = #if FABLE_COMPILER - let ref1 = makeRef () - exactEquals ref1 ref1 |> equal true + let ref1 = Erlang.makeRef () + Erlang.exactEquals ref1 ref1 |> equal true #else () #endif @@ -44,8 +44,8 @@ let ``test exactEquals on same ref`` () = [] let ``test spawn creates a process`` () = #if FABLE_COMPILER - let pid = spawn (fun () -> ()) - isProcessAlive pid |> equal true + let pid = Erlang.spawn (fun () -> ()) + Erlang.isProcessAlive pid |> equal true #else () #endif @@ -53,8 +53,8 @@ let ``test spawn creates a process`` () = [] let ``test spawnLink creates a linked process`` () = #if FABLE_COMPILER - let pid = spawnLink (fun () -> ()) - isProcessAlive pid |> equal true + let pid = Erlang.spawnLink (fun () -> ()) + Erlang.isProcessAlive pid |> equal true #else () #endif @@ -62,8 +62,8 @@ let ``test spawnLink creates a linked process`` () = [] let ``test isProcessAlive on self`` () = #if FABLE_COMPILER - let pid = self () - isProcessAlive pid |> equal true + let pid = Erlang.self () + Erlang.isProcessAlive pid |> equal true #else () #endif @@ -71,11 +71,11 @@ let ``test isProcessAlive on self`` () = [] let ``test process dictionary get/put/erase`` () = #if FABLE_COMPILER - let key = makeRef () - put key (box 42) |> ignore - let value = get key + let key = Erlang.makeRef () + Erlang.put key (box 42) |> ignore + let value = Erlang.get key value |> equal (box 42) - erase key |> ignore + Erlang.erase key |> ignore #else () #endif @@ -83,7 +83,7 @@ let ``test process dictionary get/put/erase`` () = [] let ``test send and receive`` () = #if FABLE_COMPILER - let pid = self () + let pid = Erlang.self () emitErlExpr () "erlang:self() ! {ping}" match Erlang.receive 1000 with | Some Ping -> equal 1 1 @@ -116,8 +116,8 @@ let ``test receive with data`` () = [] let ``test sendAfter and cancelTimer`` () = #if FABLE_COMPILER - let timerRef = sendAfter 60000 (box "should_not_arrive") - match cancelTimer timerRef with + let timerRef = Erlang.sendAfter 60000 (box "should_not_arrive") + match Erlang.cancelTimer timerRef with | Some remaining -> (remaining > 0) |> equal true | None -> equal "Some" "None" #else @@ -127,8 +127,8 @@ let ``test sendAfter and cancelTimer`` () = [] let ``test atomToBinary and binaryToAtom roundtrip`` () = #if FABLE_COMPILER - let atom = binaryToAtom "test_atom" - let str = atomToBinary atom + let atom = Erlang.binaryToAtom "test_atom" + let str = Erlang.atomToBinary atom str |> equal "test_atom" #else () @@ -137,10 +137,10 @@ let ``test atomToBinary and binaryToAtom roundtrip`` () = [] let ``test monitor and demonitor`` () = #if FABLE_COMPILER - let pid = spawn (fun () -> Fable.Beam.Timer.timer.sleep 60000) - let ref = monitor pid - demonitorFlush ref - exitPid pid (box "kill") + let pid = Erlang.spawn (fun () -> Fable.Beam.Timer.timer.sleep 60000) + let ref = Erlang.monitor pid + Erlang.demonitorFlush ref + Erlang.exitPid pid (box "kill") #else () #endif @@ -148,11 +148,11 @@ let ``test monitor and demonitor`` () = [] let ``test register and whereis`` () = #if FABLE_COMPILER - let name = binaryToAtom "fable_beam_test_proc" - let pid = self () - register name pid - match whereis name with - | Some found -> exactEquals pid found |> equal true + let name = Erlang.binaryToAtom "fable_beam_test_proc" + let pid = Erlang.self () + Erlang.register name pid + match Erlang.whereis name with + | Some found -> Erlang.exactEquals pid found |> equal true | None -> equal "Some" "None" #else () @@ -161,7 +161,7 @@ let ``test register and whereis`` () = [] let ``test date returns valid year month day`` () = #if FABLE_COMPILER - let (year, month, day) = date () + let (year, month, day) = Erlang.date () (year >= 2025) |> equal true (month >= 1 && month <= 12) |> equal true (day >= 1 && day <= 31) |> equal true @@ -172,10 +172,10 @@ let ``test date returns valid year month day`` () = [] let ``test dateYear dateMonth dateDay match date`` () = #if FABLE_COMPILER - let (year, month, day) = date () - dateYear () |> equal year - dateMonth () |> equal month - dateDay () |> equal day + let (year, month, day) = Erlang.date () + Erlang.dateYear () |> equal year + Erlang.dateMonth () |> equal month + Erlang.dateDay () |> equal day #else () #endif @@ -183,7 +183,7 @@ let ``test dateYear dateMonth dateDay match date`` () = [] let ``test time returns valid hour minute second`` () = #if FABLE_COMPILER - let (hour, minute, second) = time () + let (hour, minute, second) = Erlang.time () (hour >= 0 && hour <= 23) |> equal true (minute >= 0 && minute <= 59) |> equal true (second >= 0 && second <= 59) |> equal true @@ -194,7 +194,7 @@ let ``test time returns valid hour minute second`` () = [] let ``test localtime returns valid date and time`` () = #if FABLE_COMPILER - let ((year, month, day), (hour, minute, second)) = localtime () + let ((year, month, day), (hour, minute, second)) = Erlang.localtime () (year >= 2025) |> equal true (month >= 1 && month <= 12) |> equal true (day >= 1 && day <= 31) |> equal true @@ -208,7 +208,7 @@ let ``test localtime returns valid date and time`` () = [] let ``test universaltime returns valid date and time`` () = #if FABLE_COMPILER - let ((year, month, day), (hour, minute, second)) = universaltime () + let ((year, month, day), (hour, minute, second)) = Erlang.universaltime () (year >= 2025) |> equal true (month >= 1 && month <= 12) |> equal true (day >= 1 && day <= 31) |> equal true @@ -222,8 +222,8 @@ let ``test universaltime returns valid date and time`` () = [] let ``test monotonicTimeMs returns positive`` () = #if FABLE_COMPILER - let t1 = monotonicTimeMs () - let t2 = monotonicTimeMs () + let t1 = Erlang.monotonicTimeMs () + let t2 = Erlang.monotonicTimeMs () (t2 >= t1) |> equal true #else () @@ -232,8 +232,8 @@ let ``test monotonicTimeMs returns positive`` () = [] let ``test whereis returns None for unregistered name`` () = #if FABLE_COMPILER - let name = binaryToAtom "fable_beam_nonexistent_12345" - whereis name |> equal None + let name = Erlang.binaryToAtom "fable_beam_nonexistent_12345" + Erlang.whereis name |> equal None #else () #endif @@ -241,12 +241,12 @@ let ``test whereis returns None for unregistered name`` () = [] let ``test trapExit returns old value`` () = #if FABLE_COMPILER - let old1 = trapExit () + let old1 = Erlang.trapExit () // Second call should return true since we just set it - let old2 = trapExit () + let old2 = Erlang.trapExit () old2 |> equal true // Reset: set trap_exit back to false - processFlag (binaryToAtom "trap_exit") (box false) |> ignore + Erlang.processFlag (Erlang.binaryToAtom "trap_exit") (box false) |> ignore #else () #endif @@ -254,16 +254,16 @@ let ``test trapExit returns old value`` () = [] let ``test cancelTimer returns None for invalid ref`` () = #if FABLE_COMPILER - let fakeRef = makeRef () + let fakeRef = Erlang.makeRef () // cancelTimer on a non-timer ref returns None (false in Erlang) // Note: makeRef() does not create a timer ref, but we can test // that sendAfter + cancel works and returns Some - let timerRef = sendAfter 60000 (box "test") - match cancelTimer timerRef with + let timerRef = Erlang.sendAfter 60000 (box "test") + match Erlang.cancelTimer timerRef with | Some ms -> (ms >= 0) |> equal true | None -> equal "Some" "None" // Cancelling again should return None - cancelTimer timerRef |> equal None + Erlang.cancelTimer timerRef |> equal None #else () #endif @@ -271,9 +271,9 @@ let ``test cancelTimer returns None for invalid ref`` () = [] let ``test sendAfterTo sends to specific pid`` () = #if FABLE_COMPILER - let pid = self () - let timerRef = sendAfterTo 60000 pid (box "msg") - match cancelTimer timerRef with + let pid = Erlang.self () + let timerRef = Erlang.sendAfterTo 60000 pid (box "msg") + match Erlang.cancelTimer timerRef with | Some _ -> equal true true | None -> equal "Some" "None" #else @@ -283,9 +283,9 @@ let ``test sendAfterTo sends to specific pid`` () = [] let ``test byteSize returns correct size`` () = #if FABLE_COMPILER - byteSize "hello" |> equal 5 - byteSize "" |> equal 0 - byteSize "abc" |> equal 3 + Erlang.byteSize "hello" |> equal 5 + Erlang.byteSize "" |> equal 0 + Erlang.byteSize "abc" |> equal 3 #else () #endif @@ -293,13 +293,13 @@ let ``test byteSize returns correct size`` () = [] let ``test atomToList returns charlist not binary`` () = #if FABLE_COMPILER - let atom = binaryToAtom "test" - let charlist = atomToList atom + let atom = Erlang.binaryToAtom "test" + let charlist = Erlang.atomToList atom // atomToList returns a charlist (Erlang list of integers), // which is not the same as an F# string (binary). // We verify by round-tripping through listToAtom. - let atom2 = listToAtom charlist - atomToBinary atom2 |> equal "test" + let atom2 = Erlang.listToAtom charlist + Erlang.atomToBinary atom2 |> equal "test" #else () #endif diff --git a/test/TestEts.fs b/test/TestEts.fs index 7b3100a..ebeff94 100644 --- a/test/TestEts.fs +++ b/test/TestEts.fs @@ -6,13 +6,13 @@ open Fable.Beam.Testing open Fable.Core open Fable.Core.BeamInterop open Fable.Beam.Ets -open Fable.Beam.Erlang +open Fable.Beam #endif [] let ``test ets create and delete`` () = #if FABLE_COMPILER - let table = ets.new_ (binaryToAtom "test_table", [ binaryToAtom "set" ]) + let table = ets.new_ (Erlang.binaryToAtom "test_table", [ Erlang.binaryToAtom "set" ]) ets.delete table #else () @@ -21,7 +21,7 @@ let ``test ets create and delete`` () = [] let ``test ets insert and lookup`` () = #if FABLE_COMPILER - let table = ets.new_ (binaryToAtom "lookup_table", [ binaryToAtom "set" ]) + let table = ets.new_ (Erlang.binaryToAtom "lookup_table", [ Erlang.binaryToAtom "set" ]) let tuple: obj = emitErlExpr () "{1, <<\"hello\">>}" ets.insert (table, tuple) |> equal true let result = ets.lookup (table, box 1) @@ -34,7 +34,7 @@ let ``test ets insert and lookup`` () = [] let ``test ets tab2list`` () = #if FABLE_COMPILER - let table = ets.new_ (binaryToAtom "list_table", [ binaryToAtom "set" ]) + let table = ets.new_ (Erlang.binaryToAtom "list_table", [ Erlang.binaryToAtom "set" ]) let t1: obj = emitErlExpr () "{1, <<\"a\">>}" let t2: obj = emitErlExpr () "{2, <<\"b\">>}" ets.insert (table, t1) |> ignore diff --git a/test/TestGenServer.fs b/test/TestGenServer.fs index 9ad1324..34bc086 100644 --- a/test/TestGenServer.fs +++ b/test/TestGenServer.fs @@ -5,7 +5,7 @@ open Fable.Beam.Testing #if FABLE_COMPILER open Fable.Core open Fable.Core.BeamInterop -open Fable.Beam.Erlang +open Fable.Beam open Fable.Beam.GenServer #endif @@ -23,9 +23,9 @@ let ``test gen_server.stop on non-existent catches error`` () = [] let ``test gen_server.start_link returns ok with pid`` () = #if FABLE_COMPILER - let result = gen_server.start_link (binaryToAtom "test_counter_server", box 0, []) + let result = gen_server.start_link (Erlang.binaryToAtom "test_counter_server", box 0, []) match result with - | Ok pid -> isProcessAlive pid |> equal true + | Ok pid -> Erlang.isProcessAlive pid |> equal true | Error _ -> failwith "start_link should succeed" #else () @@ -34,10 +34,10 @@ let ``test gen_server.start_link returns ok with pid`` () = [] let ``test gen_server.start returns ok with pid`` () = #if FABLE_COMPILER - let result = gen_server.start (binaryToAtom "test_counter_server", box 0, []) + let result = gen_server.start (Erlang.binaryToAtom "test_counter_server", box 0, []) match result with | Ok pid -> - isProcessAlive pid |> equal true + Erlang.isProcessAlive pid |> equal true gen_server.stop (ServerRef pid) | Error _ -> failwith "start should succeed" #else @@ -47,11 +47,11 @@ let ``test gen_server.start returns ok with pid`` () = [] let ``test gen_server.call gets state`` () = #if FABLE_COMPILER - let result = gen_server.start (binaryToAtom "test_counter_server", box 42, []) + let result = gen_server.start (Erlang.binaryToAtom "test_counter_server", box 42, []) match result with | Ok pid -> - let value = gen_server.call (ServerRef pid, box (binaryToAtom "get")) - exactEquals value (box 42) |> equal true + let value = gen_server.call (ServerRef pid, box (Erlang.binaryToAtom "get")) + Erlang.exactEquals value (box 42) |> equal true gen_server.stop (ServerRef pid) | Error _ -> failwith "start should succeed" #else @@ -61,14 +61,14 @@ let ``test gen_server.call gets state`` () = [] let ``test gen_server.call increment`` () = #if FABLE_COMPILER - let result = gen_server.start (binaryToAtom "test_counter_server", box 0, []) + let result = gen_server.start (Erlang.binaryToAtom "test_counter_server", box 0, []) match result with | Ok pid -> let ref = ServerRef pid - let v1 = gen_server.call (ref, box (binaryToAtom "increment")) - exactEquals v1 (box 1) |> equal true - let v2 = gen_server.call (ref, box (binaryToAtom "increment")) - exactEquals v2 (box 2) |> equal true + let v1 = gen_server.call (ref, box (Erlang.binaryToAtom "increment")) + Erlang.exactEquals v1 (box 1) |> equal true + let v2 = gen_server.call (ref, box (Erlang.binaryToAtom "increment")) + Erlang.exactEquals v2 (box 2) |> equal true gen_server.stop ref | Error _ -> failwith "start should succeed" #else @@ -78,12 +78,12 @@ let ``test gen_server.call increment`` () = [] let ``test gen_server.call with timeout`` () = #if FABLE_COMPILER - let result = gen_server.start (binaryToAtom "test_counter_server", box 10, []) + let result = gen_server.start (Erlang.binaryToAtom "test_counter_server", box 10, []) match result with | Ok pid -> let ref = ServerRef pid - let value = gen_server.call (ref, box (binaryToAtom "get"), U2.Case1 5000) - exactEquals value (box 10) |> equal true + let value = gen_server.call (ref, box (Erlang.binaryToAtom "get"), U2.Case1 5000) + Erlang.exactEquals value (box 10) |> equal true gen_server.stop ref | Error _ -> failwith "start should succeed" #else @@ -93,7 +93,7 @@ let ``test gen_server.call with timeout`` () = [] let ``test gen_server.cast updates state`` () = #if FABLE_COMPILER - let result = gen_server.start (binaryToAtom "test_counter_server", box 0, []) + let result = gen_server.start (Erlang.binaryToAtom "test_counter_server", box 0, []) match result with | Ok pid -> let ref = ServerRef pid @@ -101,8 +101,8 @@ let ``test gen_server.cast updates state`` () = gen_server.cast (ref, setMsg) // Small delay to let cast process Fable.Beam.Timer.sleep 10 - let value = gen_server.call (ref, box (binaryToAtom "get")) - exactEquals value (box 99) |> equal true + let value = gen_server.call (ref, box (Erlang.binaryToAtom "get")) + Erlang.exactEquals value (box 99) |> equal true gen_server.stop ref | Error _ -> failwith "start should succeed" #else @@ -112,15 +112,15 @@ let ``test gen_server.cast updates state`` () = [] let ``test gen_server.stop with reason and timeout`` () = #if FABLE_COMPILER - let result = gen_server.start (binaryToAtom "test_counter_server", box 0, []) + let result = gen_server.start (Erlang.binaryToAtom "test_counter_server", box 0, []) match result with | Ok pid -> let ref = ServerRef pid - isProcessAlive pid |> equal true - gen_server.stop (ref, binaryToAtom "normal", U2.Case1 5000) + Erlang.isProcessAlive pid |> equal true + gen_server.stop (ref, Erlang.binaryToAtom "normal", U2.Case1 5000) // Process should be dead after stop Fable.Beam.Timer.sleep 10 - isProcessAlive pid |> equal false + Erlang.isProcessAlive pid |> equal false | Error _ -> failwith "start should succeed" #else ()