প্লাগইন ডেভেলপমেন্ট

একটি 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

এই নিয়মগুলো সবচেয়ে গুরুত্বপূর্ণ:

  1. api->make_* থেকে পাওয়া bnl_value* bnl-এর owned। এটি বর্তমান native-function call শেষ হওয়া পর্যন্ত থাকে। নিজে কখনো free করবেন না।
  2. list_pushmap_set deep-copy করে। কোনো value list বা map-এ attach করার পর original handle drop করা নিরাপদ — bnl copy-টা manage করে।
  3. argv[i] শুধু call-এর সময় valid। call-গুলোর মাঝখানে pointer stash করবেন না। কোনো value মনে রাখতে চাইলে এর content আপনার নিজের state-এ copy করুন।
  4. 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_loadBNL_EXPORT macro ব্যবহার করুন।
2প্লাগইনের একমাত্র compile-time bnl dependency হলো bnl/plugin.h। bnl-এর সাথে link করবেন না।
3make_* থেকে পাওয়া সব bnl_value* bnl-এর arena-owned। কখনো free করবেন না।
4list_pushmap_set parent-এ deep-copy করে।
5argv[i] value শুধু call-এর সময় valid।
6Error: api->throw_error(...), তারপর return 0। C++ throw বা longjmp না।
7bnl single-threaded। যে thread আপনার ফাংশন কল করেছে, সেটি ছাড়া অন্য থেকে api->* কল করবেন না।
8প্লাগইন DLL process-এ একবার load হয়, কখনো unload হয় না।
9bnl_load প্রতি প্লাগইন প্রতি process-এ ঠিক একবার কল হয়। সব registration এর ভেতরে রাখুন।
10ABI compatibility-এর প্রয়োজন থাকলে api->version চেক করুন। Version না বাড়িয়ে নতুন field append হতে পারে।
11module_new-এ মডিউলের নাম import name-এর সাথে মেলানো উচিত।
12interpreter বেশিক্ষণ block করবেন না। ধীর কাজ libuv-এর মাধ্যমে বা নিজের state ব্যবহার করে worker thread-এ।

C ছাড়া অন্য ভাষা

কন্ট্রাক্ট C ABI, তাই C-callable shared library তৈরি করতে পারে — এমন যেকোনো কিছুই কাজ করে:

ভাষাBuild modeনোট
Ccl /LD বা gcc -sharedসবচেয়ে সহজ পথ।
C++cl /LD বা g++ -sharedbnl_load ও function pointer-এ extern "C" ব্যবহার করুন।
Rustcrate-type = ["cdylib"]extern "C" fn bnl_load(...) + #[no_mangle]
Go-buildmode=c-sharedcgo লাগবে। Windows-এ gcc (mingw-w64) দরকার।
Zigbuild-lib -dynamicexport fn bnl_load(...)
Pascal, D, Nim, Crystallanguage-specific shared-library outputC FFI সমর্থিত যেকোনো ভাষা চলবে।

একই bnl/plugin.h এবং compiled binary-এর একই semantics সব ভাষায় প্রযোজ্য।