diff --git a/README.adoc b/README.adoc index 469b506..058898e 100644 --- a/README.adoc +++ b/README.adoc @@ -108,7 +108,56 @@ Ada's strong typing provides additional safety when calling Zig code: == Examples -See the `examples/` directory for complete working examples. +The `examples/` directory contains complete working examples: + +=== Basic Example + +Demonstrates calling Zig functions from Ada: + +[source,bash] +---- +cd examples +gprbuild -P examples.gpr basic_example.adb +./bin/basic_example +---- + +Features demonstrated: +* Version information retrieval +* Arithmetic operations (add, multiply) +* Mathematical functions (factorial, fibonacci) +* String operations + +=== Callback Example + +Demonstrates bidirectional FFI with callbacks from Zig to Ada: + +[source,bash] +---- +cd examples +gprbuild -P examples.gpr callback_example.adb +./bin/callback_example +---- + +Features demonstrated: +* Registering Ada procedures as callbacks +* Invoking callbacks from Zig +* Iterator pattern with Ada-style termination control + +=== Safety Example + +Demonstrates safety-critical patterns with error handling: + +[source,bash] +---- +cd examples +gprbuild -P examples.gpr safety_example.adb +./bin/safety_example +---- + +Features demonstrated: +* Safe division with error callbacks +* Bounds-checked array access +* Buffer operations == RSR Compliance diff --git a/examples/basic_example.adb b/examples/basic_example.adb new file mode 100644 index 0000000..f2ac4a0 --- /dev/null +++ b/examples/basic_example.adb @@ -0,0 +1,54 @@ +-- SPDX-License-Identifier: AGPL-3.0-or-later +-- Basic Example: Demonstrates calling Zig functions from Ada + +with Ada.Text_IO; +with Interfaces.C; +with Zig_FFI; + +procedure Basic_Example is + use Ada.Text_IO; + use Interfaces.C; +begin + Put_Line ("=== Ada-Zig-FFI Basic Example ==="); + Put_Line (""); + + -- Version information + declare + Version : constant unsigned := Zig_FFI.Get_Version; + Major : constant unsigned := Version / 2**16; + Minor : constant unsigned := (Version / 2**8) mod 2**8; + Patch : constant unsigned := Version mod 2**8; + begin + Put_Line ("Library Version:" & + Major'Image & "." & + Minor'Image & "." & + Patch'Image); + end; + Put_Line (""); + + -- Arithmetic operations + Put_Line ("--- Arithmetic Operations ---"); + Put_Line ("Add (5, 3) =" & Zig_FFI.Add (5, 3)'Image); + Put_Line ("Multiply (7, 6) =" & Zig_FFI.Multiply (7, 6)'Image); + Put_Line (""); + + -- Mathematical functions + Put_Line ("--- Mathematical Functions ---"); + Put_Line ("Factorial (5) =" & Zig_FFI.Factorial (5)'Image); + Put_Line ("Factorial (10) =" & Zig_FFI.Factorial (10)'Image); + Put_Line ("Fibonacci (10) =" & Zig_FFI.Fibonacci (10)'Image); + Put_Line ("Fibonacci (20) =" & Zig_FFI.Fibonacci (20)'Image); + Put_Line (""); + + -- String operations + Put_Line ("--- String Operations ---"); + declare + Test_String : constant char_array := To_C ("Hello, Ada-Zig-FFI!"); + begin + Put_Line ("String_Length (""Hello, Ada-Zig-FFI!"") =" & + Zig_FFI.String_Length (Test_String)'Image); + end; + Put_Line (""); + + Put_Line ("=== Example Complete ==="); +end Basic_Example; diff --git a/examples/callback_example.adb b/examples/callback_example.adb new file mode 100644 index 0000000..3766299 --- /dev/null +++ b/examples/callback_example.adb @@ -0,0 +1,64 @@ +-- SPDX-License-Identifier: AGPL-3.0-or-later +-- Callback Example: Demonstrates bidirectional FFI with callbacks + +with Ada.Text_IO; +with Interfaces.C; +with Zig_FFI; + +procedure Callback_Example is + use Ada.Text_IO; + use Interfaces.C; + + -- Callback procedure that will be called from Zig + procedure My_Int_Handler (Value : int) + with Convention => C; + + procedure My_Int_Handler (Value : int) is + begin + Put_Line (" [Ada callback received] Value:" & Value'Image); + end My_Int_Handler; + + -- Iterator callback that returns control signal + function My_Iterator_Handler (Value : int) return int + with Convention => C; + + function My_Iterator_Handler (Value : int) return int is + begin + Put_Line (" [Iterator] Processing:" & Value'Image); + -- Return 0 to continue, non-zero to stop + if Value >= 5 then + Put_Line (" [Iterator] Stopping at 5"); + return 1; -- Stop iteration + end if; + return 0; -- Continue + end My_Iterator_Handler; + +begin + Put_Line ("=== Ada-Zig-FFI Callback Example ==="); + Put_Line (""); + + -- Register and invoke int callback + Put_Line ("--- Int Callback Demo ---"); + Zig_FFI.Register_Int_Callback (My_Int_Handler'Access); + Put_Line ("Invoking callback with value 42:"); + Zig_FFI.Invoke_Int_Callback (42); + Put_Line ("Invoking callback with value -100:"); + Zig_FFI.Invoke_Int_Callback (-100); + Put_Line (""); + + -- Iterator pattern demonstration + Put_Line ("--- Iterator Pattern Demo ---"); + Put_Line ("Iterating from 1 to 10 with step 1 (stopping at 5):"); + declare + Count : constant int := Zig_FFI.For_Each_In_Range + (Start => 1, + Stop => 10, + Step => 1, + Handler => My_Iterator_Handler'Access); + begin + Put_Line ("Iterations completed:" & Count'Image); + end; + Put_Line (""); + + Put_Line ("=== Example Complete ==="); +end Callback_Example; diff --git a/examples/examples.gpr b/examples/examples.gpr new file mode 100644 index 0000000..d02c989 --- /dev/null +++ b/examples/examples.gpr @@ -0,0 +1,35 @@ +-- SPDX-License-Identifier: AGPL-3.0-or-later +-- Examples Project File + +with "../ada_zig_ffi.gpr"; + +project Examples is + + for Source_Dirs use ("."); + for Object_Dir use "obj"; + for Exec_Dir use "bin"; + + for Main use ("basic_example.adb", "callback_example.adb", "safety_example.adb"); + + package Compiler is + for Default_Switches ("Ada") use + ("-gnat2022", -- Ada 2022 + "-gnatwa", -- All warnings + "-gnatyM120", -- Max line length 120 + "-gnata", -- Enable assertions + "-gnatVa", -- Validity checks + "-O2"); -- Optimization + end Compiler; + + package Linker is + for Default_Switches ("Ada") use + ("-L../zig-lib/zig-out/lib", + "-lzigffi", + "-Wl,-rpath,../zig-lib/zig-out/lib"); + end Linker; + + package Binder is + for Default_Switches ("Ada") use ("-E"); + end Binder; + +end Examples; diff --git a/examples/safety_example.adb b/examples/safety_example.adb new file mode 100644 index 0000000..d3d4f28 --- /dev/null +++ b/examples/safety_example.adb @@ -0,0 +1,117 @@ +-- SPDX-License-Identifier: AGPL-3.0-or-later +-- Safety Example: Demonstrates safety-critical patterns with error handling + +with Ada.Text_IO; +with Interfaces.C; +with System; +with Zig_FFI; + +procedure Safety_Example is + use Ada.Text_IO; + use Interfaces.C; + + -- Error callback for division by zero + procedure Division_Error_Handler (Message : char_array) + with Convention => C; + + procedure Division_Error_Handler (Message : char_array) is + begin + Put_Line (" [ERROR] Division error: " & To_Ada (Message)); + end Division_Error_Handler; + + -- Bounds error callback for array access violations + procedure Bounds_Error_Handler (Index : size_t; Length : size_t) + with Convention => C; + + procedure Bounds_Error_Handler (Index : size_t; Length : size_t) is + begin + Put_Line (" [ERROR] Bounds violation: index" & + Index'Image & " >= length" & Length'Image); + end Bounds_Error_Handler; + +begin + Put_Line ("=== Ada-Zig-FFI Safety Example ==="); + Put_Line (""); + + -- Safe division demonstration + Put_Line ("--- Safe Division Demo ---"); + declare + Result : aliased int; + Status : int; + begin + -- Successful division + Put_Line ("Safe_Divide (100, 4):"); + Status := Zig_FFI.Safe_Divide + (Numerator => 100, + Denominator => 4, + Result => Result'Access, + Error_Cb => null); + if Status = 0 then + Put_Line (" Result:" & Result'Image); + end if; + + -- Division by zero with error callback + Put_Line ("Safe_Divide (50, 0) - with error callback:"); + Status := Zig_FFI.Safe_Divide + (Numerator => 50, + Denominator => 0, + Result => Result'Access, + Error_Cb => Division_Error_Handler'Access); + if Status /= 0 then + Put_Line (" Operation failed (as expected)"); + end if; + end; + Put_Line (""); + + -- Bounds-checked array access demonstration + Put_Line ("--- Bounds-Checked Array Access Demo ---"); + declare + type Int_Array is array (0 .. 4) of aliased int + with Convention => C; + + Data : Int_Array := (10, 20, 30, 40, 50); + Result : aliased int; + Status : int; + begin + -- Successful access + Put_Line ("Accessing Data (3) - valid index:"); + Status := Zig_FFI.Checked_Array_Access + (Arr => Data (0)'Address, + Len => Data'Length, + Index => 3, + Result => Result'Access, + Bounds_Error => null); + if Status = 0 then + Put_Line (" Value at index 3:" & Result'Image); + end if; + + -- Out of bounds access with error callback + Put_Line ("Accessing Data (10) - invalid index:"); + Status := Zig_FFI.Checked_Array_Access + (Arr => Data (0)'Address, + Len => Data'Length, + Index => 10, + Result => Result'Access, + Bounds_Error => Bounds_Error_Handler'Access); + if Status /= 0 then + Put_Line (" Access failed (as expected)"); + end if; + end; + Put_Line (""); + + -- Buffer sum demonstration + Put_Line ("--- Buffer Sum Demo ---"); + declare + type Byte_Array is array (0 .. 4) of aliased Interfaces.C.unsigned_char + with Convention => C; + + Data : Byte_Array := (1, 2, 3, 4, 5); + Sum : unsigned_long; + begin + Sum := Zig_FFI.Buffer_Sum (Data (0)'Address, Data'Length); + Put_Line ("Buffer_Sum ([1, 2, 3, 4, 5]) =" & Sum'Image); + end; + Put_Line (""); + + Put_Line ("=== Example Complete ==="); +end Safety_Example; diff --git a/src/zig_ffi.ads b/src/zig_ffi.ads index 6af3819..13ffc1c 100644 --- a/src/zig_ffi.ads +++ b/src/zig_ffi.ads @@ -11,6 +11,7 @@ -- Result : Interfaces.C.int := Zig_FFI.Add (2, 3); with Interfaces.C; +with System; use Interfaces.C; package Zig_FFI is @@ -60,6 +61,109 @@ package Zig_FFI is Convention => C, External_Name => "buffer_sum"; + -- ========================================================================== + -- CALLBACK TYPES (Zig -> Ada) + -- ========================================================================== + + -- Callback procedure types for bidirectional FFI + type Int_Procedure is access procedure (Value : int) + with Convention => C; + + type Long_Procedure is access procedure (Value : long) + with Convention => C; + + type Status_Callback is access procedure (Code : int; Message : char_array) + with Convention => C; + + type Int_Handler is access function (Value : int) return int + with Convention => C; + + type Error_Callback is access procedure (Message : char_array) + with Convention => C; + + type Bounds_Error_Callback is access procedure (Index : size_t; Length : size_t) + with Convention => C; + + -- ========================================================================== + -- CALLBACK REGISTRATION (Ada -> Zig) + -- ========================================================================== + + procedure Register_Int_Callback (Cb : Int_Procedure) + with Import => True, + Convention => C, + External_Name => "register_int_callback"; + + procedure Register_Long_Callback (Cb : Long_Procedure) + with Import => True, + Convention => C, + External_Name => "register_long_callback"; + + procedure Register_Status_Callback (Cb : Status_Callback) + with Import => True, + Convention => C, + External_Name => "register_status_callback"; + + -- ========================================================================== + -- CALLBACK INVOCATION (Trigger Zig to call Ada) + -- ========================================================================== + + procedure Invoke_Int_Callback (Value : int) + with Import => True, + Convention => C, + External_Name => "invoke_int_callback"; + + procedure Invoke_Long_Callback (Value : long) + with Import => True, + Convention => C, + External_Name => "invoke_long_callback"; + + procedure Invoke_Status_Callback (Code : int; Message : char_array) + with Import => True, + Convention => C, + External_Name => "invoke_status_callback"; + + -- ========================================================================== + -- ITERATOR PATTERN + -- ========================================================================== + + -- Iterate over a range with Ada-style control + -- Handler returns 0 to continue, non-zero to stop + function For_Each_In_Range + (Start : int; + Stop : int; + Step : int; + Handler : Int_Handler) return int + with Import => True, + Convention => C, + External_Name => "for_each_in_range"; + + -- ========================================================================== + -- SAFETY-CRITICAL PATTERNS + -- ========================================================================== + + -- Safe division with error callback on division by zero + -- Returns 0 on success, -1 on error + function Safe_Divide + (Numerator : int; + Denominator : int; + Result : access int; + Error_Cb : Error_Callback) return int + with Import => True, + Convention => C, + External_Name => "safe_divide"; + + -- Bounds-checked array access with error callback on violation + -- Returns 0 on success, -1 on bounds error + function Checked_Array_Access + (Arr : System.Address; + Len : size_t; + Index : size_t; + Result : access int; + Bounds_Error : Bounds_Error_Callback) return int + with Import => True, + Convention => C, + External_Name => "checked_array_access"; + private -- Internal implementation details diff --git a/zig-lib/build.zig b/zig-lib/build.zig new file mode 100644 index 0000000..2ad8ad9 --- /dev/null +++ b/zig-lib/build.zig @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Ada-Zig-FFI Build Configuration + +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // Build the shared library for FFI + const lib = b.addSharedLibrary(.{ + .name = "zigffi", + .root_source_file = b.path("src/lib.zig"), + .target = target, + .optimize = optimize, + }); + + // Also build a static library for static linking + const static_lib = b.addStaticLibrary(.{ + .name = "zigffi", + .root_source_file = b.path("src/lib.zig"), + .target = target, + .optimize = optimize, + }); + + // Install artifacts + b.installArtifact(lib); + b.installArtifact(static_lib); + + // Run tests + const lib_unit_tests = b.addTest(.{ + .root_source_file = b.path("src/lib.zig"), + .target = target, + .optimize = optimize, + }); + + const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); + + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_lib_unit_tests.step); +} diff --git a/zig-lib/src/lib.zig b/zig-lib/src/lib.zig index 61d0e3e..252b058 100644 --- a/zig-lib/src/lib.zig +++ b/zig-lib/src/lib.zig @@ -57,6 +57,14 @@ export fn string_length(str: [*:0]const u8) callconv(.C) usize { return std.mem.len(str); } +export fn buffer_sum(ptr: [*]const u8, len: usize) callconv(.C) c_ulong { + var sum: c_ulong = 0; + for (0..len) |i| { + sum += ptr[i]; + } + return sum; +} + // ============================================================================ // CALLBACK SUPPORT (Zig -> Ada) // Ada side uses: type Callback is access procedure (Value : Integer); @@ -188,3 +196,19 @@ test "safe_divide error" { const status = safe_divide(10, 0, &result, null); try std.testing.expectEqual(@as(c_int, -1), status); } + +test "buffer_sum" { + const data = [_]u8{ 1, 2, 3, 4, 5 }; + try std.testing.expectEqual(@as(c_ulong, 15), buffer_sum(&data, data.len)); +} + +test "fibonacci" { + try std.testing.expectEqual(@as(c_ulong, 0), fibonacci(0)); + try std.testing.expectEqual(@as(c_ulong, 1), fibonacci(1)); + try std.testing.expectEqual(@as(c_ulong, 55), fibonacci(10)); +} + +test "string_length" { + const str: [*:0]const u8 = "hello"; + try std.testing.expectEqual(@as(usize, 5), string_length(str)); +}