This document explains how to use JCL from C and other languages via the C FFI.
Overview
The JCL C FFI provides a stable, C-compatible API for embedding JCL in other languages. This allows you to:
- Parse and validate JCL configuration files from C/C++ applications
- Format JCL code programmatically
- Run lint checks and get code quality feedback
- Generate documentation from JCL source files
- Embed JCL in applications written in C, C++, Go, Python, Ruby, etc.
Building
Quick Build
./build-ffi.sh
Manual Build
cargo build --release --features ffi
This produces:
target/release/libjcl.so(Linux)target/release/libjcl.dylib(macOS)target/release/jcl.dll(Windows)
Installation
System-wide Installation (Linux/macOS)
# Copy library
sudo cp target/release/libjcl.* /usr/local/lib/
# Copy header
sudo cp include/jcl.h /usr/local/include/
# Update library cache (Linux only)
sudo ldconfig
Project-local Installation
# Copy to your project
cp target/release/libjcl.* your-project/lib/
cp include/jcl.h your-project/include/
Usage
Basic Example (C)
#include <jcl.h>
#include <stdio.h>
int main() {
// Initialize
jcl_init();
// Parse code
const char* source = "x = 42\ny = x + 1";
JclResult result = jcl_parse(source);
if (result.success) {
printf("Parse successful: %s\n", result.value);
} else {
printf("Parse error: %s\n", result.error);
}
// Clean up
jcl_free_result(&result);
return 0;
}
Compilation
# With system-installed library
gcc -o myapp myapp.c -ljcl
# With project-local library
gcc -o myapp myapp.c -I./include -L./lib -ljcl
# Run (Linux)
LD_LIBRARY_PATH=./lib ./myapp
# Run (macOS)
DYLD_LIBRARY_PATH=./lib ./myapp
API Reference
Initialization
int jcl_init(void);
Initialize the JCL library. Must be called before using any other functions. Returns 0 on success.
Parse
JclResult jcl_parse(const char* source);
Validate JCL syntax. Returns result with success status.
Format
JclResult jcl_format(const char* source);
Auto-format JCL source code. Returns formatted code on success.
Lint
JclResult jcl_lint(const char* source);
Check code for style issues. Returns JSON array of lint issues.
Generate Documentation
JclResult jcl_generate_docs(const char* source, const char* module_name);
Extract documentation from source code. Returns Markdown documentation.
Version
const char* jcl_version(void);
Get JCL version string. Returns static string (do NOT free).
Memory Management
void jcl_free_result(JclResult* result);
void jcl_free_string(char* ptr);
Free memory allocated by JCL. Always call jcl_free_result() after using a JclResult.
JclResult Structure
typedef struct {
bool success; // True if operation succeeded
char* value; // Result value (NULL on error)
char* error; // Error message (NULL on success)
} JclResult;
Language Bindings
C++
#include <jcl.h>
#include <iostream>
#include <memory>
class JclResultGuard {
JclResult result;
public:
JclResultGuard(JclResult r) : result(r) {}
~JclResultGuard() { jcl_free_result(&result); }
bool success() const { return result.success; }
const char* value() const { return result.value; }
const char* error() const { return result.error; }
};
int main() {
jcl_init();
JclResultGuard result(jcl_parse("x = 42"));
if (result.success()) {
std::cout << "Success: " << result.value() << std::endl;
} else {
std::cerr << "Error: " << result.error() << std::endl;
}
return 0;
}
Python (ctypes)
import ctypes
import os
# Load library
lib = ctypes.CDLL("./libjcl.so")
# Define structures
class JclResult(ctypes.Structure):
_fields_ = [
("success", ctypes.c_bool),
("value", ctypes.c_char_p),
("error", ctypes.c_char_p)
]
# Define functions
lib.jcf_init.restype = ctypes.c_int
lib.jcf_parse.argtypes = [ctypes.c_char_p]
lib.jcf_parse.restype = JclResult
lib.jcf_free_result.argtypes = [ctypes.POINTER(JclResult)]
# Initialize
lib.jcf_init()
# Parse
source = b"x = 42\ny = x + 1"
result = lib.jcf_parse(source)
if result.success:
print(f"Success: {result.value.decode('utf-8')}")
else:
print(f"Error: {result.error.decode('utf-8')}")
# Clean up
lib.jcf_free_result(ctypes.byref(result))
Go
package main
/*
#cgo LDFLAGS: -L. -ljcl
#include "jcl.h"
#include <stdlib.h>
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
C.jcf_init()
source := C.CString("x = 42\ny = x + 1")
defer C.free(unsafe.Pointer(source))
result := C.jcf_parse(source)
defer C.jcf_free_result(&result)
if result.success {
fmt.Println("Success:", C.GoString(result.value))
} else {
fmt.Println("Error:", C.GoString(result.error))
}
}
Ruby (FFI)
require 'ffi'
module JCL
extend FFI::Library
ffi_lib './libjcl.so'
class Result < FFI::Struct
layout :success, :bool,
:value, :pointer,
:error, :pointer
end
attach_function :jcl_init, [], :int
attach_function :jcl_parse, [:string], Result.by_value
attach_function :jcl_free_result, [Result.ptr], :void
end
JCL.jcf_init
result = JCL.jcf_parse("x = 42\ny = x + 1")
if result[:success]
puts "Success: #{result[:value].read_string}"
else
puts "Error: #{result[:error].read_string}"
end
JCL.jcf_free_result(result.pointer)
Examples
See examples/c/example.c for a comprehensive example demonstrating all API functions.
To build and run:
cd examples/c
gcc -o example example.c -I../../include -L../../target/release -ljcl
LD_LIBRARY_PATH=../../target/release ./example
Thread Safety
The current implementation is not thread-safe. If you need to use JCL from multiple threads:
- Use separate JCL instances per thread (not yet supported)
- Serialize access with mutexes
- Process JCL operations in a dedicated thread
Future versions may add thread-safe APIs.
Memory Management
Rules
- Always free JclResult: Call
jcl_free_result()after using anyJclResult - Don’t free version string: The string from
jcl_version()is static - Don’t use after free: Pointers become invalid after freeing
- No double-free: Free each result exactly once
Example
JclResult result = jcl_parse(source);
// Use result
if (result.success) {
printf("%s\n", result.value);
}
// Free exactly once
jcl_free_result(&result);
// Don't use result.value or result.error after this point!
Error Handling
All functions return JclResult with:
success == trueandvalue != NULLon successsuccess == falseanderror != NULLon error
Always check the success field before accessing value or error.
Performance
- Parse: ~1ms for typical files (1-10KB)
- Format: ~2ms for typical files
- Lint: ~5ms for typical files
- Memory: ~1-5MB overhead
Performance is comparable to the native Rust implementation.
Debugging
Check Library Loading
# Linux
ldd myapp
nm -D libjcl.so | grep jcl_
# macOS
otool -L myapp
nm -g libjcl.dylib | grep jcl_
Enable Debug Output
# Build with debug symbols
cargo build --features ffi
# Run with debug info
RUST_BACKTRACE=1 ./myapp
Limitations
Compared to the native Rust API, the C FFI:
❌ Not Available:
- File I/O functions (filesystem operations)
- Direct AST manipulation
- Custom function registration
- Streaming/async operations
✅ Available:
- All parsing and formatting features
- Full linter with all rules
- Documentation generation
- All basic JCL functionality
Building for Distribution
Static Linking
For fully static binaries:
RUSTFLAGS='-C target-feature=+crt-static' cargo build --release --target x86_64-unknown-linux-gnu --features ffi
Stripping Debug Symbols
cargo build --release --features ffi
strip target/release/libjcl.so
Cross-Compilation
# Install target
rustup target add x86_64-pc-windows-gnu
# Build
cargo build --release --target x86_64-pc-windows-gnu --features ffi
Troubleshooting
“Library not found” error
Make sure the library is in your library path:
# Linux
export LD_LIBRARY_PATH=/path/to/lib:$LD_LIBRARY_PATH
# macOS
export DYLD_LIBRARY_PATH=/path/to/lib:$DYLD_LIBRARY_PATH
# Or install system-wide
sudo cp libjcl.* /usr/local/lib/
sudo ldconfig # Linux only
Compilation errors
Make sure the header is in your include path:
gcc -I/path/to/include -L/path/to/lib -ljcl myapp.c
Crashes or segfaults
Common causes:
- Not freeing results (memory leak)
- Using pointers after freeing
- Passing NULL to functions that don’t allow it
- Double-freeing results
Use valgrind to debug:
valgrind --leak-check=full ./myapp
Contributing
Contributions welcome! Areas for improvement:
- Thread-safe API
- Async operations
- Direct AST access
- Custom function registration
- Bindings for more languages
- Performance optimizations
- Better error messages
License
Same as JCL project (MIT OR Apache-2.0)