Browse Source

Add initial code and README

master
Alex Williams 2 months ago
parent
commit
8adaf772a5
Signed by: aw GPG Key ID: 19EE4AAA361A7E2C
6 changed files with 210 additions and 0 deletions
  1. +3
    -0
      .gitignore
  2. +9
    -0
      Cargo.toml
  3. +9
    -0
      Makefile
  4. +120
    -0
      README.md
  5. +32
    -0
      extract.l
  6. +37
    -0
      src/lib.rs

+ 3
- 0
.gitignore View File

@@ -0,0 +1,3 @@
target/
rust-1*
rustc-1*

+ 9
- 0
Cargo.toml View File

@@ -0,0 +1,9 @@
[package]
name = "pilrust"
version = "0.1.0"
authors = ["Alex Williams, On-Prem <license@on-premises.com>"]
edition = "2018"
license = "MIT"

[lib]
crate-type = ["cdylib"]

+ 9
- 0
Makefile View File

@@ -0,0 +1,9 @@
# Makefile to build the lib and test it

.PHONY: all

all: check

check:
cargo build && \
./extract.l

+ 120
- 0
README.md View File

@@ -0,0 +1,120 @@
# PicoLisp FFI with Rust

This repo provides a simple example of how to use [PicoLisp](https://software-lab.de/down.html) with [Rust](https://www.rust-lang.org/tools/install) using PicoLisp's FFI `(native)` functionality.

# Requirements

* Rustc and Cargo `v1.47.0+`
* PicoLisp 64-bit `v17.12+` or `pil21`

# Getting started

Once you've setup _PicoLisp_ and _Rust_, simply type `make` to build and test the shared library.

# Output

Before I explain what's going on, here's what the output should look like:

```
Received struct: PilStruct {
byte1: 32,
byte2: 33,
character1: 67,
character2: 68,
int: -1,
long: 1,
string: 0x4847464544434241,
array: [
1,
2,
3,
4,
5,
6,
7,
8,
],
}
Result code: 0
Extracted struct:
(de Extracted (42 43)
("A" "B")
65535
9223372036854775807
"pilrust"
(80 105 99 111 76 105 115 112) )
```

# Explain

The code can be found in [extract.l](extract.l) and [src/lib.rs](src/lib.rs). The _Rust_ code is designed as a **shared library** and can be called by PicoLisp's **(native)** function to pass data to/from between both languages.

## PicoLisp code explanation

First, the code allocates 32 bytes of memory, which will be used to store data in a [struct](https://software-lab.de/doc/refS.html#struct).

It then creates a struct named `P` with the following specification:

* 2 arbitrary bytes
* 2-bytes containing valid UTF-8 characters
* 1x 32-bit (4 bytes) signed integer
* 1x 64-bit (8 bytes) signed long
* 1x 8-byte null-terminated string
* 1x 8-byte arbitrary bytes array

Then the following [native](https://software-lab.de/doc/refN.html#native) call is made and its result is stored in the `Res` variable:

```picolisp
(native "target/debug/libpilrust.so" "extract" 'I P)
```

This calls the `extract` function from the _Rust_ library, with the `P` struct as its only parameter. It expects a 32-bit signed integer `I` as the return value (it will be either `0` or `-1`).

Next, the code will extract the `P` structure using the specification described above:

```
(struct P '((B . 2) (C . 2) I N S (B . 8)))
```

Finally, the code will free the previously allocated memory and print the result of the `P` structure.

Some tests run at the end to ensure the data received from _Rust_ is what we expected.

### Note

* The values sent to the _Rust_ library will be printed by _Rust_ as `Received struct:`.
* The values received from the _Rust_ library will be printed by _PicoLisp_ as `Extracted struct:`.

## Rust code explanation

The _Rust_ code defines the struct for the received data; it is named `PilStruct` and contains the exact same specification as the `P` struct in the _PicoLisp code explanation_.

The `extract()` function creates a new struct in the variable `newstruct` which contains some new values, different from what was received by _PicoLisp_.

Since FFI is considered [unsafe in _Rust_](https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html), the code which actually does the FFI (dereferencing the pointer and writing to it) is contained in an `unsafe` block:

```
unsafe { ... playing with fire ... }
```

Luckily, the _Rust_ function checks if the struct is a **null pointer** before trying to work with it, and returns `-1` if it is. However it doesn't check if it's correctly aligned (it is), so look-out for that!

The code then dereferences the pointer and prints what it received (the entire struct) from _PicoLisp_ as `Received struct:` (mentioned earlier).

Finally, it writes the `newstruct` struct to the pointer and returns `0`. _PicoLisp_ can then read the return code and the new struct data.

# Thoughts

There isn't much to this code, but I thought it would be fun to create a working FFI library that's _not_ written in _C_ and which works perfectly with _PicoLisp_.

Enjoy!

# Contributing

* For bugs, issues, or feature requests, please [create an issue](https://github.com/aw/picolisp-rust/issues/new).

# License

[MIT License](LICENSE)

Copyright (c) 2020 Alexander Williams, On-Prem <license@on-premises.com>

+ 32
- 0
extract.l View File

@@ -0,0 +1,32 @@
#!/usr/bin/env pil

(setq P (native "@" "malloc" 'N 32))

# initialize the struct
(struct P 'N
32 (char "!") # 2 bytes
67 (char "D") # 2 chars
255 255 255 255 # int
1 0 0 0 0 0 0 0 # long
65 66 67 68 69 70 71 72 # string
1 2 3 4 5 6 7 8 ) # char array

(let Res (native "target/debug/libpilrust.so" "extract" 'I P)
(prinl "Result code: " Res)

(if (=0 Res)
(let Extracted (struct P '((B . 2) (C . 2) I N S (B . 8)))
(native "@" "free" NIL P)
(prinl "Extracted struct: ")
(pp 'Extracted)

# tests
(test (42 43) (car Extracted))
(test '("A" "B") (; Extracted 2))
(test 65535 (; Extracted 3))
(test 9223372036854775807 (; Extracted 4))
(test "pilrust" (; Extracted 5))
(test "PicoLisp" (pack (mapcar char (last Extracted)))) )
(bye 1) ]

(bye)

+ 37
- 0
src/lib.rs View File

@@ -0,0 +1,37 @@
use std::os::raw::*;

#[repr(C)]
#[derive(Debug)]
pub struct PilStruct {
byte1: c_char,
byte2: c_char,
character1: c_char,
character2: c_char,
int: c_int,
long: c_long,
string: *const c_char,
array: [u8; 8],
}

#[no_mangle]
pub extern "C" fn extract(c_struct: *mut PilStruct) -> i32 {
let newstruct = PilStruct {
byte1: 42,
byte2: 43,
character1: 'A' as c_char,
character2: 'B' as c_char,
int: 65535,
long: 9223372036854775807,
string: "pilrust\x00".as_ptr(),
array: [80, 105, 99, 111, 76, 105, 115, 112],
};
unsafe {
match c_struct.is_null() {
true => return -1,
_ => {},
}
println!("Received struct: {:#?}", &*c_struct);
std::ptr::write(c_struct, newstruct)
}
0 // return code
}

Loading…
Cancel
Save