প্লাগইন ডেভেলপমেন্ট
একটি bnl প্লাগইন হলো .dll / .so / .dylib শেয়ার্ড লাইব্রেরি যা bnl-এর C প্লাগইন কন্ট্রাক্ট implement করে। প্লাগইন রানটাইম এক্সটেন্ড করে — কোনো C লাইব্রেরি wrap করুন (libcurl, SQLite, OpenCV…), হার্ডওয়্যার SDK expose করুন, বা কোনো compiled ভাষায় দ্রুত inner loop implement করুন। স্ট্যান্ডার্ড লাইব্রেরি মডিউলের মতই এগুলো bnl-এ import হয়।
কন্ট্রাক্ট হলো একটি একক C হেডার, bnl/plugin.h। নিজের প্রজেক্টে drop করুন, একটি C ফাংশন লিখুন, শেয়ার্ড লাইব্রেরিতে compile করুন। কোনো bnl রানটাইমের সাথে link করতে হয় না; load time-এ bnl একটি function-pointer table দেয়, সব কিছু সেটার মাধ্যমে হয়।
কখন প্লাগইন লিখবেন
| আপনি চান… | টুল |
|---|---|
| bnl থেকে কোনো third-party C লাইব্রেরি (libcurl, OpenSSL, SDL ইত্যাদি) ব্যবহার করতে | প্লাগইন |
| interpreted bnl-এ ধীর কোনো algorithm implement করতে | প্লাগইন |
| bnl binding না-থাকা কোনো হার্ডওয়্যার/OS SDK bridge করতে | প্লাগইন |
| bpm-এ reusable মডিউল ship করতে | প্লাগইন |
| system লাইব্রেরির একটি ফাংশন কল করতে | প্লাগইন (v1-এ আলাদা FFI মডিউল নেই) |
যা যা লাগবে
| ফাইল | উৎস |
|---|---|
bnl/plugin.h | একক drop-in C হেডার। release archive থেকে নিন, বা bnl repo-এর include/bnl/ থেকে copy করুন। |
আপনার প্লাগইন সোর্স (.c / .cpp / .rs / .go / .zig) | নিজে লিখুন। সাধারণ মডিউলের জন্য ~50 লাইন। |
| আপনার ভাষার compiler | যেটাই বেছে নিন। কোনো bnl-specific toolchain নেই। |
bnl (প্লাগইন run করতে) | ইনস্টল করা আছে। |
আপনার দরকার নেই: bnl_core.lib, অন্য কোনো bnl হেডার, একই bnl version (একই BNL_PLUGIN_API_VERSION থাকলেই হলো), বা একই MSVC version।
কুইকস্টার্ট
my-plugin/
├── bnl/
│ └── plugin.h ← drop-in header
└── mathx.c ← প্লাগইন সোর্স
mathx.c — সবচেয়ে ছোট কার্যকর প্লাগইন:
#include <math.h>
#include "bnl/plugin.h"
static bnl_value* cube(const bnl_api* api,
int argc, bnl_value** argv, void* ud) {
(void)argc; (void)ud;
double x = api->get_number(argv[0]);
return api->make_number(api, x * x * x);
}
static bnl_value* hypot_fn(const bnl_api* api,
int argc, bnl_value** argv, void* ud) {
(void)argc; (void)ud;
double a = api->get_number(argv[0]);
double b = api->get_number(argv[1]);
return api->make_number(api, sqrt(a*a + b*b));
}
BNL_EXPORT bnl_module* bnl_load(const bnl_api* api) {
bnl_module* m = api->module_new(api, "mathx");
api->module_add_function(m, "cube", 1, cube, 0);
api->module_add_function(m, "hypot", 2, hypot_fn, 0);
api->module_add_value (m, "greeting",
api->make_string(api, "hi from C", 9));
return m;
}
Compile — প্রতি OS-এ এক লাইন:
# Windows (MSVC, x64 Developer Command Prompt)
cl /LD /O2 /I. mathx.c
# Linux (gcc)
gcc -shared -fPIC -O2 -I. -o mathx.so mathx.c -lm
# macOS (clang)
clang -shared -O2 -I. -o mathx.dylib mathx.c -lm
Import — .dll/.so/.dylib কোনো bnl স্ক্রিপ্টের পাশে রেখে দিন:
import "./mathx.dll" as m; // বা "./mathx.so" / "./mathx.dylib"
print(m.cube(4)); // 64
print(m.hypot(3, 4)); // 5
print(m.greeting); // hi from C
এই পুরো loop এতটুকুই। প্লাগইনের ABI surface শুধু export করা নামগুলো (bnl_load, cube, hypot_fn, …) এবং সেটা যে bnl_api function-pointer table পায়।
bnl_api রেফারেন্স
bnl_load একটি bnl_api struct-এর pointer পায় যাতে function pointer থাকে। প্লাগইন value তৈরি, argument inspect, মডিউল গঠন, ও error raise — সব এই table-এর মাধ্যমে করে। bnl-এর কোনো direct linker reference নেই।
Values
| Constructor | ফেরত |
|---|---|
api->make_null(api) | bnl null |
api->make_bool(api, b) | bnl bool (0/1) |
api->make_number(api, n) | bnl number (double) |
api->make_string(api, s, len) | bnl string (byte-গণনাকৃত; NUL-terminated না) |
api->make_list(api) | খালি bnl list |
api->make_map(api) | খালি bnl map |
| Inspector | ফেরত |
|---|---|
api->get_type(v) | BNL_TYPE_NULL/BOOL/NUMBER/STRING/LIST/MAP/OTHER |
api->get_bool(v) | int (0/1) |
api->get_number(v) | double |
api->get_string(v, &len) | byte pointer + length (NUL-terminated না) |
Lists
| ফাংশন | কাজ |
|---|---|
api->list_length(list) | size_t |
api->list_get(api, list, i) | i-তে element, বা out of range হলে NULL |
api->list_push(list, item) | append (deep-copy করে list-এ) |
Maps
| ফাংশন | কাজ |
|---|---|
api->map_size(map) | size_t |
api->map_has(map, key) | 0 বা 1 |
api->map_get(api, map, key) | value বা NULL |
api->map_set(map, key, value) | upsert (deep-copy করে map-এ) |
api->map_key_at(map, i, &key, &len) | index দিয়ে key iterate |
মডিউল গঠন
| ফাংশন | কাজ |
|---|---|
api->module_new(api, name) | মডিউল শুরু |
api->module_add_function(m, name, arity, fn, ud) | ফাংশন bind (arity হলো fixed arg count, বা variadic-এর জন্য -1) |
api->module_add_value(m, name, v) | constant bind (string / number / map / list) |
Errors
api->throw_error(api, "useful message");
return 0; // এর পরে আর কোনো কাজ undefined
bnl-এ error সাধারণ exception হিসেবে surface হয়, যা try / catch দিয়ে ধরা যায়:
try {
m.must_be_pos(-1);
} catch (e) {
print(e); // "plugin function 'must_be_pos': must_be_pos: value is negative"
}
মেমরি ownership
এই নিয়মগুলো সবচেয়ে গুরুত্বপূর্ণ:
api->make_*থেকে পাওয়াbnl_value*bnl-এর owned। এটি বর্তমান native-function call শেষ হওয়া পর্যন্ত থাকে। নিজে কখনো free করবেন না।list_pushওmap_setdeep-copy করে। কোনো value list বা map-এ attach করার পর original handle drop করা নিরাপদ — bnl copy-টা manage করে।argv[i]শুধু call-এর সময় valid। call-গুলোর মাঝখানে pointer stash করবেন না। কোনো value মনে রাখতে চাইলে এর content আপনার নিজের state-এ copy করুন।module_newথেকে পাওয়াbnl_module*bnl-এর owned। আপনি মডিউল free করবেন না; শুধুbnl_loadথেকে return করবেন।
এই নিয়মগুলো মেনে চললে bnl-side কোনো leak বা double-free হবে না।
Error ও exception
api->throw_error(api, msg) বর্তমান call-এর context-এ error record করে। তারপর প্লাগইন ফাংশন return 0 (NULL) দেবে। call unwind হলে bnl আপনার message সহ runtime exception raise করে। C longjmp, C++ throw, বা অন্য কোনো unwinding mechanism ব্যবহার করবেন না — সেগুলো bnl-এর error path bypass করে এবং interpreter crash করাতে পারে।
ক্রস-প্ল্যাটফর্ম shipping
Linux-এ build করা প্লাগইন শুধু Linux-এ load হয়; প্রতি OS-এর জন্য একটি করে binary লাগবে। দু'টি সাধারণ pattern:
Pattern A — প্রতি OS-এর জন্য আলাদা archive। প্রতিটি archive-এ মিলে যাওয়া binary ও একটি bnl.json থাকে যেখানে native field সেই file-এর নাম বলে:
mathx-1.0.0-windows-x64.zip → bnl.json (native: "mathx.dll") + mathx.dll
mathx-1.0.0-linux-x64.tar.gz → bnl.json (native: "libmathx.so") + libmathx.so
mathx-1.0.0-macos-arm64.tar.gz → bnl.json (native: "libmathx.dylib") + libmathx.dylib
ব্যবহারকারী তার OS-এর সাথে মিলে যাওয়া archive download করেন। bpm-ও একই pattern follow করে: install time-এ host platform-এর সঠিক archive বেছে নেয়।
Pattern B — একটি fat archive। একটি archive-এ সব OS-এর binary + per-OS bnl.json override থাকে। runtime per-OS resolution support দরকার; v1 contract-এ নেই, পরিকল্পিত।
আপাতত Pattern A হলো recommended publishing format।
bpm দিয়ে install
native plugin একটি bnl প্রজেক্টে pure-bnl প্যাকেজের মতই install হয়:
bpm add mathx
bpm platform-অনুযায়ী archive fetch করে deps/mathx/-এ extract করে, এমন layout রেখে:
my-app/
├── bnl.json ← project marker
├── main.bnl
└── deps/
└── mathx/
├── bnl.json ← {"name": "mathx", "native": "mathx.dll"}
└── mathx.dll
main.bnl থেকে:
import "mathx" as m;
print(m.cube(4)); // 64
bnl-এর module resolver main.bnl-এর directory থেকে walk up করে, deps/mathx/ পায়, bnl.json পড়ে, native field দেখে, এবং shared library load করে। dep হাতে রাখা হোক বা bpm install করা হোক — user-facing experience একই।
এক নজরে নিয়ম
| # | নিয়ম |
|---|---|
| 1 | ঠিক একটি symbol export করুন: bnl_load। BNL_EXPORT macro ব্যবহার করুন। |
| 2 | প্লাগইনের একমাত্র compile-time bnl dependency হলো bnl/plugin.h। bnl-এর সাথে link করবেন না। |
| 3 | make_* থেকে পাওয়া সব bnl_value* bnl-এর arena-owned। কখনো free করবেন না। |
| 4 | list_push ও map_set parent-এ deep-copy করে। |
| 5 | argv[i] value শুধু call-এর সময় valid। |
| 6 | Error: api->throw_error(...), তারপর return 0। C++ throw বা longjmp না। |
| 7 | bnl single-threaded। যে thread আপনার ফাংশন কল করেছে, সেটি ছাড়া অন্য থেকে api->* কল করবেন না। |
| 8 | প্লাগইন DLL process-এ একবার load হয়, কখনো unload হয় না। |
| 9 | bnl_load প্রতি প্লাগইন প্রতি process-এ ঠিক একবার কল হয়। সব registration এর ভেতরে রাখুন। |
| 10 | ABI compatibility-এর প্রয়োজন থাকলে api->version চেক করুন। Version না বাড়িয়ে নতুন field append হতে পারে। |
| 11 | module_new-এ মডিউলের নাম import name-এর সাথে মেলানো উচিত। |
| 12 | interpreter বেশিক্ষণ block করবেন না। ধীর কাজ libuv-এর মাধ্যমে বা নিজের state ব্যবহার করে worker thread-এ। |
C ছাড়া অন্য ভাষা
কন্ট্রাক্ট C ABI, তাই C-callable shared library তৈরি করতে পারে — এমন যেকোনো কিছুই কাজ করে:
| ভাষা | Build mode | নোট |
|---|---|---|
| C | cl /LD বা gcc -shared | সবচেয়ে সহজ পথ। |
| C++ | cl /LD বা g++ -shared | bnl_load ও function pointer-এ extern "C" ব্যবহার করুন। |
| Rust | crate-type = ["cdylib"] | extern "C" fn bnl_load(...) + #[no_mangle]। |
| Go | -buildmode=c-shared | cgo লাগবে। Windows-এ gcc (mingw-w64) দরকার। |
| Zig | build-lib -dynamic | export fn bnl_load(...)। |
| Pascal, D, Nim, Crystal | language-specific shared-library output | C FFI সমর্থিত যেকোনো ভাষা চলবে। |
একই bnl/plugin.h এবং compiled binary-এর একই semantics সব ভাষায় প্রযোজ্য।