لغات البرمجة الشائعة تفتقر للمرونة وهذا أدى خلال العقود الماضية لبروز العشرات من لغات البرمجة التي تتشابه في الجزء الأكبر منها وتختلف عادة في الجزئية التي أراد مطوروا اللغة تقديمها للمبرمجين كأداة جديدة لسد حاجة معينة. هذا الوضع يثقل كاهل المبرمجين بالحاجة لتعلم العديد من اللغات أو استخدام عدة لغات حتى في المشروع الواحد. وكلما تظهر تقنية جديدة ومعها لغة جديدة يحتاج المبرمج لتعلم هذه اللغة من الصفر ومعظم هذا الجهد يضيع على علم مكرر بما أن معظم ما في اللغة تكرار لنفس الفكرة الموجودة في من سبقها من اللغات، كما يحتاج مبرمجو الأدوات والمكتبات لدعم هذه اللغات الجديدة وهذا مقدار كبير من الجهود الضائعة من قبل جميع الأطراف، وزيادة كبيرة في التعقيد والتشتيت في صناعة البرمجيات.
تحل لغة الأسُس هذه المشكلة بجعل اللغة مرنة وخواصها قابلة للإضافة ديناميكيا، أي أثناء ترجمة برنامج المستخدم دون الحاجة لإعادة بناء المترجم أو إجراء تغيير في إعداداته، وهذا يوفر الكثير من الوقت والجهد لكل من منتجي التقنية ومستخدميها. فمنتجوا التقنيات الجديدة لن يحتاجوا لإنشاء لغة كاملة من الصفر، وإنما يكتفون بإنشاء الخاصية الجديدة التي يحتاجونها ونشرها في مكتبة مستقلة كإضافة للغة الأسس. هذه الإضافات لها حرية شبه كاملة في تعديل اللغة مثل إضافة قواعد جديدة أو تعديل شفرة بناء برنامج المستخدم إلى شفرة تنفيذية أو ما شابه.
مرونة لغة الأسس تتيح ربطها بعدة واجهات تنفيذية، أي يمكن لأجزاء مختلفة من البرنامج الواحد أن تُترجم أو تنفذ باستخدام واجهات تنفيذية مختلفة. الفكرة معاكسة لأطُر كـ LLVM و JVM و .NET. على سبيل المثال يوفر إطار LLVM وحدة إنشاء شفرة تنفيذية مفردة يُربط بها واجهات أمامية للغات متعددة. الأسُس عكس ذلك، فهي واجهة لغة موحدة يُربط بها العديد من وحدات إنشاء الشفرة التنفيذية حسب حاجة البرنامج.
الربط مع واجهات مختلفة يمكن أن يتم من شفرة المبرمج نفسها، أو من مكتبة يشملها المبرمج في برنامجه. تتيح هذه الإمكانية للمبرمج إبقاء مشروعه موحدًا في برنامج واحد وتصنيف الأجزاء المختلفة منه تبعًا لوظائفها وليس تبعًا للبيئة التي ستُنفذها، كما سنرى في أمثلة لاحقة. كما تتيح هذه الإمكانية إنشاء مكتبات أكثر شمولًا مما هو ممكن في اللغات الشائعة. على سبيل المثال، يمكن لمكتبة أن تحتوي على شفرة واجهة المستخدم لخاصية معينة، مع شفرة الخادم التابعة لها في مكتبة واحدة دون الحاجة لتجزئتها إلى مكتبة للخادم وأخرى لواجهة المستخدم.توجد ثلاث اختلافات مهمة بين الأسُس واللغات التقليدية فيما يتعلق بقابلية التوسيع:
المثال التالي يوضح خاصية الدالات المغلفة (closure) في لغة الأسس. مترجم لغة الأسس لا يدعم الدالات المغلفة مبدئيًا، ولا يفهم الكلمة المفتاحية `مغلفة` (`closure`) لكن المكتبة التي تُحمل في بداية المثال تضيف الخاصية مع القواعد المطلوبة لها. يُلاحظ في المثال أن المغلفة تُميز تلقائيًا بين الأنواع المختلفة من المتغيرات المستخدمة في متن المغلفة وتعمل على تجهيز الحمولة المطلوبة بناءًا على ذلك، فتضع في حمولة المغلفة المتغير `ح` (`p`) لأنه متغير محلي بينما لا تُضمن المتغير `ع` (`g`) لأنه متغير عمومي وكذلك لا تُضمن المتغير `ك` (`q`) لأنه غير مستخدم في متن الدالة. هذه الدرجة من التحكم في توسعات اللغة مستحيلة في اللغات الشائعة لأنها تتطلب توافقًا تشغيليا بين المكتبة والمترجم.
اشمل "مـتم/طـرفية"؛ اشمل "مغلفة"؛ استخدم مـتم.طـرفية؛ عرف ع: صـحيح؛ دالة هات_مغلفة (): (مغلفة (صـحيح)) { عرف ح: صـحيح = 2؛ عرف ك: صـحيح = 4؛ أرجع مغلفة (م: صـحيح) { اطبع("معطى المغلفة: %d\ج"، م)؛ اطبع("متغير حمولة: %d\ج"، ح)؛ اطبع("متغير عمومي: %d\ج"، ع)؛ }؛ } عرف د: مغلفة(صـحيح) = هات_مغلفة()؛ ع = 1؛ د(3)؛ // الناتج: // معطى المغلفة: 3 // متغير حمولة: 2 // متغير عمومي: 1
import "Srl/Console"; import "closure"; use Srl.Console; def g: Int; func getClosure (): (closure (Int)) { def p: Int = 2; def q: Int = 4; return closure (a: Int) { print("closure arg: %d\n", a); print("closure payload var: %d\n", p); print("global var: %d\n", g); }; } def c: closure(Int) = getClosure(); g = 1; c(3); // Output: // closure arg: 3 // closure payload var: 2 // global var: 1
المثال التالي يوضح إضافة خصائص جديدة لتسهيل الربط مع قواعد البيانات وكتابة العبارات الاستعلامية. نلاحظ في المثال أنه يبدأ بشمول مدير الحزم، الذي يستخدمه لتنزيل وشمول مكتبة من الشبكة العنكبوتية مباشرة (في هذه الحالة من موقع جت هب) وهذه المكتبة بدورها تضيف خصائص جديدة لمطابقة الأصناف مع ما يقابلها من جداول في قاعدة البيانات، كما تضيف قواعد لكتابة الشروط في العبارات الاستعلامية. ففي دالة `أضف_سيارة` (`addCar`) ترى أن المستخدم لا يحتاج لكتابة عبارات SQL لإضافة القيد الجديد وإنما تستنبطها المكتبة تلقائيًا من المبدلات التي تسم الصنف ومتغيراته، أما في دالة `جد_سيارات` (`findCars`) فتجد شرط البحث مكتوبًا بلغة الأسس نفسها كما لو كنت تكتب شرطًا في جملة شرطية، ولكن المترجم يترجم هذا الشرط إلى عبارة SQL.
اشمل "مـتم/نـص"؛ اشمل "مـتم/نـص"؛ اشمل "مـتم/مـصفوفة"؛ اشمل "مـتم/طـرفية"؛ اشمل "مـتم/نـظام"؛ اشمل "مـتم/سندات"؛ اشمل "مـحا"؛ مـحا.اشمل_ملف("Alusus/Rows"، { "صـفوف.أسس"، "مـشغلات/بـوستغرس.أسس" })؛ استخدم مـتم؛ استخدم صـفوف؛ @جدول["سيارات"] صنف سـيارة { عرف_أساسيات_الجدول[]؛ @إلزامي @فهرس_رئيسي @مـعرف_عالمي @حقل عرف المعرف: صحيح؛ @مـحارف_مرنة["50"] @حقل عرف الاسم: نـص؛ @عـدد_عائم @حقل["سعر_السيارة"] عرف السعر: عائم؛ } عرف قب: قـاعدة_بيانات(مـشغل_بوستغرس(مـعطيات_الاتصال().{ اسم_قاعدة_البيانات = "alusus"؛ اسم_المستخدم = "alusus"؛ كلمة_السر = "alusus"؛ عنوان_الخادم = "0.0.0.0"؛ المنفذ = 5432؛ }))؛ إذا !قب.أمتصل() { نـظام.فشل(1، نـص("فشل الاتصال بقاعدة البيانات: ") + قب.هات_آخر_خطأ())؛ } قب.مهيكل[سـيارة].أنشئ()؛ دالة أضف_سيارة(الاسم: نـص, السعر: عـائم) { عرف س: سـيارة؛ س.المعرف = أنشئ_معرفا_عالميا()؛ س.الاسم = الاسم؛ س.السعر = السعر؛ قب.احفظ[سـيارة](س)؛ } دالة جد_سيارات(أقصى_سعر: عـائم): لـا_مضمون[مـصفوفة[سـندنا[سـيارة]]] { أرجع قب.من[سـيارة].حيثما[السعر <= أقصى_سعر].اجلب()؛ }
import "Apm"; Apm.importFile("Alusus/Rows", { "Rows.alusus", "Drivers/Postgresql.alusus" }); use Srl; use Rows; @model["cars"] class Car { define_model_essentials[]; @notNull @primaryKey @Uuid @column def id: String; @VarChar["50"] @column def name: String; @Float @column["car_price"] def price: Float; } def db: Db(PostgresqlDriver(ConnectionParams().{ dbName = "alusus"; userName = "alusus"; password = "alusus"; host = "0.0.0.0"; port = 5432; })); if !db.isConnected() { System.fail(1, String("Error connecting to DB: ") + db.getLastError()); } db.schemaBuilder[Car].create(); function addCar(name: String, price: Float) { def c: Car; c.id = generateUuid(); c.name = name; c.price = price; db.save[Car](c); } function findCars(maxPrice: Float): Possible[Array[SrdRef[Car]]] { return db.from[Car].where[price <= maxPrice].select(); }
المثال التالي يوضح كتابة برنامج ويب متكامل يحتوي ضمن ملف واحد على شفرة الخادم وواجهة المستخدم. يبدأ المثال باستخدام مدير الحزم لتحميل مكتبة مـنصة_ويب. من خلال المبدلات المضافة إلى الدالات تعلم مـنصة_ويب أي الدالات ستشغل في الخادم وأي منها في واجهة المستخدم، فتبحث في شفرة الشمروع تلقائيًا عن الدالات التي ستنفذ في الخادم وتضيف استدعاءاتها إلى دالة الخادم الرئيسية، وتبحث أيضًا عن الدالات التي ستنفذ في المتصفح وتترجمها إلى شفرة ويب أسمبلي (WebAssembly) وتجعلها متوفرة للتحميل من الخادم على المنفذ المحدد في مبدل `@منفذ_مرئي` (`@uiEndpoint`). في هذا المثال دالتا `أضف_رسالة` (`postMessage`) و `هات_الرسائل` (`getMessages`) ستترجمان إلى منافذ بيانية على الخادم تحت المسار `/messages`، بينما ستترجم دالة `رئيسي` (`main`) إلى ويب أسمبلي وتوفر على المنفذ `/`. ومن الخطط المستقبلية لهذه المكتبة ترجمة أي استدعاء لدالة خادم تلقائيًا إلى عملية HTTP.
اشمل "مـتم/نـظام"؛ اشمل "بـناء"؛ اشمل "مـحا"؛ مـحا.اشمل_ملف("Alusus/WebPlatform"، "مـنصة_ويب.أسس")؛ مـحا.اشمل_ملف("Alusus/Http"، "بـننف.أسس")؛ مـحا.اشمل_ملف("Alusus/Json"، "جـيسون.أسس")؛ اشمل "مـتم/سندات"؛ اشمل "مـتم/مـصفوفة"؛ اشمل "مـتم/نـص"؛ اشمل "مـتم/طـرفية"؛ اشمل "مغلفة"؛ استخدم مـتم؛ استخدم مـنصة_ويب؛ //============================================================================== // الخادم عرف _حد_الرسائل_: 12؛ عرف رسائل : مـصفوفة[نـص]؛ @منفذ_بياني["POST"، "/messages"] دالة أضف_رسالة (اتصال:مؤشر[بـننف.اتـصال]){ عرف بيانات: مصفوفة[مـحرف،1024]؛ عرف حجم_البيانات: صحيح = بـننف.اقرأ(اتصال، بيانات~مؤشر، بيانات~حجم)؛ إذا رسائل.هات_الطول() >= _حد_الرسائل_ رسائل.أزل(0)؛ رسائل.أضف(نـص(بيانات~مؤشر، حجم_البيانات))؛ بـننف.اطبع(اتصال، "HTTP/1.1 200 Ok\r\n\r\n")؛ } @منفذ_بياني["GET"، "/messages"] دالة هات_الرسائل (اتصال: مؤشر[بـننف.اتـصال]) { عرف الرد: نـص = نـص.ادمج(رسائل، "<br>")؛ بـننف.اطبع(اتصال، "HTTP/1.1 200 Ok\r\n")؛ بـننف.اطبع(اتصال، "Content-Type: text/plain\r\n")؛ بـننف.اطبع(اتصال، "Cache-Control: no-cache\r\n")؛ بـننف.اطبع(اتصال، "Content-Length: %d\r\n\r\n"، الرد.هات_الطول())؛ بـننف.اطبع(اتصال، الرد.صوان)؛ } //============================================================================= // صـفحات واجهة المستخدم عرف _جلب_: "GET"؛ عرف _إرسال_: "POST"؛ عرف _المسار_: "/messages"؛ عرف _ترويسة_صنف_بيانات_نصي_: "Content-Type: application/text"؛ @منفذ_مرئي["/"] @عنوان["مثال منصة ويب - الدردشة"] دالة رئيسي { عرف عند_استلام_بيانات: مغلفة (جـيسون)؛ نـافذة.النموذج.حدد_المشهد(صـندوق({}).{ الطراز.{ الطول = مـسافة.مئوي(100)؛ ملء_السطر = مـلء_سطر._مسافة_بينية_؛ الاتجاه = اتـجاه._من_اليمين_؛ الإظهار = إظـهار._مرن_؛ النسق = نـسق._عمود_؛ }؛ أضف_فروع({ الـترويسة()، صـندوق().{ الطراز.{ العرض = مـسافة.مئوي(100)؛ الحشوة = مـسافة4.نقاط(5)؛ الإظهار = إظـهار._مرن_؛ النسق = نـسق._عمود_؛ المرونة = مـرونة(1، 1)؛ } أضف_فروع({ كـتابة().{ الطراز.{ العرض = مـسافة.مئوي(100)؛ الطول = مـسافة.مئوي(100)؛ لون_الخط = لـون(50، 50، 50)؛ حجم_الخط = مـسافة.نقاط(20.0)؛ }؛ عند_استلام_بيانات = مغلفة (جيسون: جـيسون) { عرف الحالة: صحيح = جيسون .هات_كائن("eventData") .هات_صحيح("status")؛ إذا الحالة >= 200 و الحالة &tt; 300 { عرف البيانات: نـص = جيسون .هات_كائن("eventData") .هات_نص("body")؛ إذا هذا.هات_النص() != البيانات { هذا.حدد_النص(البيانات)؛ } } وإلا { // تتمة: أظهر إشعار خطأ. } }؛ } })؛ }، مـدخل_نصي().{ العرض = مـسافة.مئوي(100)؛ الطول = مـسافة.نقاط(50)؛ قد_تم_الإدخال = مغلفة (بيانات_جديده: نـص) { أرسل_نداء( _إرسال_، _المسار_، _ترويسة_صنف_بيانات_نصي_، بيانات_جديده، 10000، مغلفة(جـيسون) {} )؛ أرسل_نداء(_جلب_، _المسار_، 0، 0، 500، عند_استلام_بيانات)؛ }؛ } })؛ })؛ ابدأ_المؤقت_المتكرر(500000، مغلفة (جـيسون) { أرسل_نداء(_جلب_، _المسار_، 0، 0، 500، عند_استلام_بيانات)؛ })؛ نفذ_حلقة_معالجة_الأحداث()؛ } //============================================================================= // البداية طـرفية.اطبع("تشغيل الخادم على المنفذ 8010...\nURL: http://localhost:8010/\n")؛ شغل_الخادم ({ "listening_ports"، "8010"، "static_file_max_age"، "0" })؛
import "Build"; import "Apm"; Apm.importFile("Alusus/WebPlatform"); use Srl; use WebPlatform; //============================================================================== // Backend def MAX_MESSAGES: 12; def messages: Array[String]; @beEndpoint["POST", "/messages"] func postMessage (conn: ptr[Http.Connection]) { def postData: array[Char, 1024]; def postDataSize: Int = Http.read(conn, postData~ptr, postData~size); if messages.getLength() >= MAX_MESSAGES messages.remove(0); messages.add(String(postData~ptr, postDataSize)); Http.print(conn, "HTTP/1.1 200 Ok\r\n\r\n"); } @beEndpoint["GET", "/messages"] func getMessages (conn: ptr[Http.Connection]) { def response: String = String.merge(messages, "<br>"); Http.print(conn, "HTTP/1.1 200 Ok\r\n"); Http.print(conn, "Content-Type: text/plain\r\n"); Http.print(conn, "Cache-Control: no-cache\r\n"); Http.print(conn, "Content-Length: %d\r\n\r\n", response.getLength()); Http.print(conn, response.buf); } //============================================================================== // Frontend Pages @uiEndpoint["/"] @title["WebPlatform Example - Chat"] func main { def onFetch: closure (json: Json); Window.instance.setView(Box({}).{ style.{ height = Length.percent(100); justify = Justify.SPACE_BETWEEN; display = Display.FLEX; layout = Layout.COLUMN; }; addChildren({ Header(), Box({}).{ style.{ width = Length.percent(100) - Length.pt(10); padding = Length4.pt(5); display = Display.FLEX; layout = Layout.COLUMN; flex = Flex(1); }; addChildren({ Text(String()).{ style.{ width = Length.percent(100); height = Length.percent(100); fontColor = Color(50, 50, 50); fontSize = Length.pt(20.0); }; onFetch = closure (json: Json) { def status: Int = json .getObject("eventData") .getInt("status"); if status >= 200 and status < 300 { def data: String = json .getObject("eventData") .getString("body"); if this.getText() != data { this.setText(data); } } else { // TODO: Notify user. } }; } }); }, TextEntry().{ width = Length.percent(100) - Length.pt(3); height = Length.pt(50); onNewEntry = closure (newData: String) { sendRequest( "POST", "/messages", "Content-Type: application/text", newData, 10000, closure (Json) {} ); sendRequest("GET", "/messages", null, null, 500, onFetch); }; } }) }); startTimer(500000, closure (json: Json) { sendRequest("GET", "/messages", null, null, 500, onFetch); }); runEventLoop(); } //============================================================================== // Project Control Console.print("Starting server on port 8010...\nURL: http://localhost:8010/\n"); runServer({ "listening_ports", "8010", "static_file_max_age", "0" });
لا حدود لما يمكن للمكتبات فعله ولا حدود لعدد أو نوع المكتبات المحملة آنياً وقد صُممت لغة الأسُس بطريقة تسمح بإضافة عدد لا محدد من القواعد اللغوية وتُقلل احتمال تصادمها ببعض وتضمن أيضاً تناسقها. راجع تصميم اللغة في صفحة الوثائق لمزيد من المعلومات.