Support Typed Jerboa bytes inputs
ober
7c3b0176f4a173c7ccb56f2ff5d6874b1bf30a03
--- a/docs/jerboa-to-rust.md +++ b/docs/jerboa-to-rust.md @@ -232,9 +232,10 @@ should get separate ABI wrappers. Current landing: scalar ABI-safe exported functions get generated Rust `extern "C"` wrappers, and `.ss` Jerboa wrapper files call those symbols through -Chez `foreign-procedure`. `String` arguments can cross this boundary for -scalar-return functions as UTF-8 bytevector-plus-length pairs. Non-scalar -returns and owned values still wait for opaque handles and conversion records. +Chez `foreign-procedure`. `String` and `Bytes` arguments can cross this +boundary for scalar-return functions as bytevector-plus-length pairs, with +`String` using UTF-8 conversion. Non-scalar returns and owned values still wait +for opaque handles and conversion records. ## Generics @@ -569,11 +570,12 @@ Second module: typed `rope`. - Support `Bool`, `Int`, `Nat`, `String`, `Bytes`. - Support function calls. -- Support `if`, `let`, arithmetic, comparisons. Initial `string-length` - support landed as a checked `(String -> Nat)` builtin. -- Generate wrappers. Initial scalar `.ss` wrappers and `String` argument - wrappers landed; record, variant, string return, bytes, option, result, and - handle conversions remain future work. +- Support `if`, `let`, arithmetic, comparisons. Initial `string-length` and + `bytevector-length` support landed as checked `(String -> Nat)` and + `(Bytes -> Nat)` builtins. +- Generate wrappers. Initial scalar `.ss` wrappers plus `String` and `Bytes` + argument wrappers landed; record, variant, string/bytes return, option, + result, and handle conversions remain future work. ### Milestone 3: Records and Variants --- a/docs/typed-jerboa.md +++ b/docs/typed-jerboa.md @@ -160,7 +160,8 @@ Current landing: The wrappers load the compiled Rust cdylib from `JERBOA_TYPED_RUST_LIB`, validate dynamic arguments, call `foreign-procedure`, convert `Char` values through unsigned code points, and pass `String` arguments as UTF-8 - bytevector-plus-length pairs for scalar-return functions. + bytevector-plus-length pairs and `Bytes` arguments as bytevector-plus-length + pairs for scalar-return functions. - `make typed-wrapper-smoke` builds the primitive Rust fixture, generates its wrapper, loads the cdylib, and calls the generated Jerboa functions through Chez FFI. @@ -177,7 +178,8 @@ Current landing: the same module, generated record/variant operations, and exhaustive `match` over same-module variants. The first builtin string primitive, `string-length`, is checked as `(String -> Nat)` and lowers to Rust - `.len()`. Imported calls and richer forms are reported as unsupported. It + `.len()`; `bytevector-length` is checked as `(Bytes -> Nat)` and lowers the + same way. Imported calls and richer forms are reported as unsupported. It does not yet resolve imports, lower to typed core IR, or emit LLVM. ## Surface Syntax @@ -812,11 +814,11 @@ Minimum excluded features: - Compile generated Rust as a static or dynamic library. Initial disposable `rlib`/`staticlib`/`cdylib` builds landed. - Generate conversion functions. Initial scalar argument checks, `Char` - code-point conversions, and `String` argument UTF-8 bytevector conversions - landed at the wrapper boundary. + code-point conversions, `String` argument UTF-8 bytevector conversions, and + `Bytes` argument bytevector conversions landed at the wrapper boundary. - Generate Jerboa wrappers. Initial `.ss` wrapper generation landed for - scalar ABI-safe exported functions and `String` arguments with scalar - returns. + scalar ABI-safe exported functions, plus `String` and `Bytes` arguments with + scalar returns. ### Milestone 4: First Real Module --- a/lib/jerboa/typed/checker.ss +++ b/lib/jerboa/typed/checker.ss @@ -204,7 +204,9 @@ (def builtin-call-signatures (list (cons 'string-length - (make-typed-call-sig (list 'String) 'Nat)))) + (make-typed-call-sig (list 'String) 'Nat)) + (cons 'bytevector-length + (make-typed-call-sig (list 'Bytes) 'Nat)))) (def (field-types fields) (map typed-field-type fields)) --- a/lib/jerboa/typed/rust.ss +++ b/lib/jerboa/typed/rust.ss @@ -314,7 +314,7 @@ (def (abi-safe-param-type? type) (or (abi-safe-type? type) - (eq? type 'String))) + (memq type '(String Bytes)))) (def (abi-rust-type type) (case type @@ -355,7 +355,7 @@ (def (emit-abi-param param) (let ([name (rust-symbol-name (typed-param-name param))]) (case (typed-param-type param) - [(String) + [(String Bytes) (list (string-append name "_ptr: *const u8") (string-append name "_len: usize"))] @@ -377,7 +377,7 @@ " = char::from_u32(" name ").unwrap_or('\\u{FFFD}');"))] - [(String) + [(String Bytes) (write-line port 1 (string-append "let " name " = {")) (write-line port 2 (string-append @@ -396,7 +396,10 @@ name "_len) }")) (write-line port 2 "};") - (write-line port 2 "String::from_utf8_lossy(bytes).into_owned()") + (write-line port 2 + (if (eq? (typed-param-type param) 'String) + "String::from_utf8_lossy(bytes).into_owned()" + "bytes.to_vec()")) (write-line port 1 "};")] [else #f]))) @@ -614,6 +617,14 @@ (emit-expression (car args)) ").len() as u64")) + (def (emit-bytevector-length args) + (unless (= (length args) 1) + (error 'typed-rust "bytevector-length expects one operand" args)) + (string-append + "(" + (emit-expression (car args)) + ").len() as u64")) + (def (emit-call name args) (let ([record-constructor (lookup-record-constructor name)] [record-accessor (lookup-record-accessor name)] @@ -631,6 +642,8 @@ [variant-predicate "true"] [(eq? name 'string-length) (emit-string-length args)] + [(eq? name 'bytevector-length) + (emit-bytevector-length args)] [else (string-append (rust-symbol-name name) --- a/lib/jerboa/typed/wrapper.ss +++ b/lib/jerboa/typed/wrapper.ss @@ -83,7 +83,7 @@ (def (abi-chez-argument-types type) (case type - [(String) '(u8* size_t)] + [(String Bytes) '(u8* size_t)] [else (list (abi-chez-type type))])) (def (typed-module-wrapper-file-name module) @@ -121,6 +121,7 @@ [(Fixnum) (string-append "(fixnum? " name ")")] [(Float) (string-append "(real? " name ")")] [(String) (string-append "(string? " name ")")] + [(Bytes) (string-append "(bytevector? " name ")")] [else (error 'typed-wrapper "unsupported ABI parameter type" (typed-param-type param))]))) @@ -166,6 +167,10 @@ (list bytes-name (string-append "(bytevector-length " bytes-name ")")))] + [(Bytes) + (list + name + (string-append "(bytevector-length " name ")"))] [else (list name)]))) (def (wrapper-call-expression def) --- a/tests/fixtures/typed/rust-basic.ss +++ b/tests/fixtures/typed/rust-basic.ss @@ -1,5 +1,5 @@ (typed-library (sample typed rust-basic) - (export zero add-one positive? choose greeting double-add text-length) + (export zero add-one positive? choose greeting double-add text-length bytes-length) (def (zero) : Nat 0) @@ -21,4 +21,7 @@ (+ y 1))) (def (text-length (text : String)) : Nat - (string-length text))) + (string-length text)) + + (def (bytes-length (data : Bytes)) : Nat + (bytevector-length data))) --- a/tests/test-typed-checker.ss +++ b/tests/test-typed-checker.ss @@ -287,6 +287,22 @@ (string-length x)))) '(argument-type-mismatch)) +(test "builtin bytevector-length returns Nat" + (error-kinds + '(typed-library (body bytevector-length-ok) + (export f) + (def (f (x : Bytes)) : Nat + (bytevector-length x)))) + '()) + +(test "builtin bytevector-length rejects non-Bytes" + (error-kinds + '(typed-library (body bytevector-length-bad) + (export f) + (def (f (x : String)) : Nat + (bytevector-length x)))) + '(argument-type-mismatch)) + (test "record constructor and accessor calls" (error-kinds '(typed-library (body record-ok) --- a/tests/test-typed-rust.ss +++ b/tests/test-typed-rust.ss @@ -65,6 +65,15 @@ (define string-rust "// Generated by Jerboa's typed Rust backend. Do not edit.\n#![deny(unsafe_op_in_unsafe_fn)]\n#![allow(unused_parens)]\n#![allow(unused_variables)]\n\npub fn text_length(s: String) -> u64 {\n (s).len() as u64\n}\n\n#[unsafe(no_mangle)]\npub extern \"C\" fn jt_sample_typed_text_text_length(s_ptr: *const u8, s_len: usize) -> u64 {\n let s = {\n let bytes: &[u8] = if s_ptr.is_null() {\n &[]\n } else {\n // unsafe: pointer and length are produced by the generated Jerboa wrapper.\n unsafe { std::slice::from_raw_parts(s_ptr, s_len) }\n };\n String::from_utf8_lossy(bytes).into_owned()\n };\n text_length(s)\n}\n\n") +(define bytes-form + '(typed-library (sample typed bytes) + (export bytes-length) + (def (bytes-length (data : Bytes)) : Nat + (bytevector-length data)))) + +(define bytes-rust + "// Generated by Jerboa's typed Rust backend. Do not edit.\n#![deny(unsafe_op_in_unsafe_fn)]\n#![allow(unused_parens)]\n#![allow(unused_variables)]\n\npub fn bytes_length(data: Vec<u8>) -> u64 {\n (data).len() as u64\n}\n\n#[unsafe(no_mangle)]\npub extern \"C\" fn jt_sample_typed_bytes_bytes_length(data_ptr: *const u8, data_len: usize) -> u64 {\n let data = {\n let bytes: &[u8] = if data_ptr.is_null() {\n &[]\n } else {\n // unsafe: pointer and length are produced by the generated Jerboa wrapper.\n unsafe { std::slice::from_raw_parts(data_ptr, data_len) }\n };\n bytes.to_vec()\n };\n bytes_length(data)\n}\n\n") + (define ops-form '(typed-library (sample typed ops) (export pane-id make-insert make-noop edit-size) @@ -110,6 +119,10 @@ (typed-library-form->rust-string string-form) string-rust) +(test "rust emits bytes input ABI conversions" + (typed-library-form->rust-string bytes-form) + bytes-rust) + (test "rust lowers record and variant operations" (typed-library-form->rust-string ops-form) ops-rust) --- a/tests/test-typed-wrapper-e2e.ss +++ b/tests/test-typed-wrapper-e2e.ss @@ -32,6 +32,7 @@ (check "choose" (= (choose #t) 1)) (check "double-add" (= (double-add 20) 41)) (check "text-length" (= (text-length "hello") 5)) +(check "bytes-length" (= (bytes-length (make-bytevector 7 0)) 7)) (printf "~%Typed wrapper FFI smoke: ~a passed, ~a failed~%" pass fail) (when (> fail 0) --- a/tests/test-typed-wrappers.ss +++ b/tests/test-typed-wrappers.ss @@ -51,6 +51,15 @@ (define string-wrapper (typed-library-form->jerboa-wrapper-string string-form)) +(define bytes-form + '(typed-library (sample typed bytes) + (export bytes-length) + (def (bytes-length (data : Bytes)) : Nat + (bytevector-length data)))) + +(define bytes-wrapper + (typed-library-form->jerboa-wrapper-string bytes-form)) + (printf "--- Typed Jerboa wrapper tests ---~%") (test "wrapper maps Unit to void" @@ -88,6 +97,21 @@ "(%text_length %s_bytes (bytevector-length %s_bytes))") #t) +(test "wrapper binds Bytes as u8* plus size_t" + (substring? bytes-wrapper + "(foreign-procedure \"jt_sample_typed_bytes_bytes_length\" (u8* size_t) unsigned-64)") + #t) + +(test "wrapper validates Bytes args as bytevectors" + (substring? bytes-wrapper + "(unless (bytevector? data)") + #t) + +(test "wrapper passes Bytes directly with length" + (substring? bytes-wrapper + "(%bytes_length data (bytevector-length data))") + #t) + (printf "~%Typed wrapper: ~a passed, ~a failed~%" pass fail) (when (> fail 0) (exit 1))