لوآ (زبان برنامه‌نویسی)

تاریخچه

لوا در سال ۱۳۷۱ توسط روبرتو یروسالیمچی، لوییز هنریک دی فیگوئردو و والدمر سلس، اعضای تِکگِراف - گروه تکنولوژی گرافیک کامپیوتر (TecGraph) در PUC-Rio، دانشگاه مسیحی ریو د ژانیرو در برزیل ساخته شد.
از سال ۱۳۵۵ تا ۱۳۷۰ در برزیل قوانین سخت تجارت خارجی روی سخت‌افزار و نرم‌افزار وجود داشت. این قوانین از انگیزه ملی توانایی ساخت نرم‌افزار و سخت‌افزار در داخل برزیل نشات گرفته بود. این قوانین موجب شد که تکگراف نه از نظر اقتصادی و نه سیاسی امکان داشته باشد که بتواند نرم‌افزار آماده شده از خارج کشور خریداری کند. به جز این مسئله، جدایی جغرافیای برزیل از بقیه مراکز تحقیقات و توسعه، موجب شد که تکگراف ابزارهایی که نیاز داشت را از هیچ تولید کند.
«پدر و مادر» تاریخی لوا زبان‌های توصیف داده/تنظیم (SOL (Simple Object Language و (DEL (Date Entry Language هستند. آن دو به‌طور مستقل در سال‌هال ۱۳۷۰–۱۳۷۱ در تکگراف تولید شده‌اند، تا دو پروژهٔ متفاوت را منعطف‌تر کنند (هر دوی این پروژه‌ها برنامه‌های گرافیکی تعاملی برای کاربردهای مهندسی در شرکت پتروبرس بودند. در SOL و DEL ساختارهای کنترلی وجود نداشت و نیاز به یک زبان برنامه‌سازی کامل‌تر احساس می‌شد. «در ۱۳۷۱ تنها رقیب اصلی لوا تیکل بود، که به‌طور صریح برای استفاده نهفته در برنامه‌ها طراحی شده بود. اما تیکل نحوی ناآشنا داشت، پشتیبانی کافی از توصیف داده نداشت و فقط روی محیط یونیکس قابل استفاده بود. ما لیسپ و شما را استفاده نکردیم چون نحو زشتی داشتند. پایتون هنوز در نوزادی به سر می‌برد. در جو آزاد و مستقل تکگراف تقریباً عادی بود که ما باید زبان پردازه‌نویسی خودمان را اختراع می‌کردیم… به این علت که خیلی از کاربران محتمل زیان برنامه‌نویسان حرفه‌ای نبودند، زبان باید از نحو و مفاهیم مرموز خودداری می‌کرد. پیاده‌سازی زبان جدید باید قابلیت اجرا روی محیط‌های مختلف را می‌داشت چون کاربران تکگراف انواع متمایزی رایانه در اختیار داشتند. در نهایت، از آنچه که ما انتظار داشتیم که محصولات دیگر تکگراف هم نیاز به نهفتن یک زبان پردازه‌نویسی داشتند، زبان جدید باید مثال SOL را ادامه می‌داد و به عنوان کتاب‌خانه‌ای همراه API سی ارائه می‌شد.» 
در نتیجه لوا به دنیا آمد. سازنده‌های اشیا لوای ۱٫۰ که آن زمان کمی متفاوت از حالت سبُک و انعطاف‌پذیر امروزی متفاوت بودند، نحو توصیف دادهٔ SOL را به کار گرفته بودند (ریشه نام لوا از همین‌جا می‌آید، sol در زبان پرتغالی یه معنای خورشید است و لوا به معنای ماه). ساختارهای کنترلی لوا (if,while,repeat/until) بیشتر از مدولا قرض گرفته شده بودند، اما از CLU هم تأثیر گرفته‌است (مقدار خروجی چندگانه از توابع و عملیات مقداردهی چندگانه
به عنوان گزینه‌ای ساده‌تر از پارامترهای ارجاعی یا اشاره‌گرهای صریح).
لوا همچنین از ++C (تعریف محدودتر حوزه متغیرها)، آوک و سنوبال (آرایه‌های انجمنی) هم تأثیر گرفته‌است.
در یک مقاله منتشر شده در مجله «Dr Dobb's Journal»، سازندگان لوا همچنین اذعان می‌کنند که زبان لیسپ و شما هم با داده ساختار تکین خود (لیست) تأثیر مهمی روی تصمیم سازندگان بر استفاده از جدول به عنوان داده ساختار اولیه داشتند.
مفاهیم فعلی لوا بیشتر از شما گرفته شده‌اند: «از نظر مفهوم لوا شباهت‌های زیادی به شما دارد، گرچه این شباهت‌ها مستقیماً دیده نمی‌شوند، چون این دو زبان تفاوت‌های نحوی زیادی دارند. تأثیر شما روی لوا به تدریج در زمان تکامل لوا افزایش یافته‌است: در ابتدا شما فقط زبانی در پس‌زمینه بود اما بعداً به تدریج به عنوان منبعی برای ایده گرفتن مهم شد، به خصوص به خاطر معرفی توابع ناشناس و حوزه‌بندی کامل»

ساختار کلی

دستورات لوا خط به خط توسط مفسر اجرا می‌شوند. در لوا همچون زبان‌های پردازه‌نویسی دیگر تابع اصلی (main) وجود ندارد (می‌توان اینطور تصور کرد که کل برنامه یک تابع اصلی است). هر بلوک کد لوا یا مربوط به مقداردهای متغیر است یا اینکه اجرای یک تابع خاص می‌باشد. ارتباط با دنیای بیرون از طریق رابط برنامه‌نویسی نرم‌افزار انجام می‌شود.
متغیرهایی که تعریف می‌شوند به دو دسته عمومی و محلی تقسیم می‌شوند. متغیرهای محلی متغیرهایی هستند که حوزه آن‌ها یک تابع خاص است. در این زبان پیش‌پردازنده وجود ندارد.
در لوا مستقیماً قابلیت‌های زیادی وجود ندارد، بلکه هر برنامه‌نویس با فرا ساز و کارهای تعبیه شده می‌تواند قابلیت لازم خود را اضافه کند. این فرا ساز و کارها عبارتند از: آرایه‌های انجمنی پویا (Dynamic Associative Arrays)، ابزارهای انعطاف‌پذیر (Reflexive Facilities) و فراجدول‌ها (Metatables)

کد نمونه

کدهای این قسمت همگی کامل هستند و بدون هیچ تغییری تفسیر می‌شوند.
برنامهٔ نوشتن عبارت "Hello World" بر روی صفحه:
print('Hello World') -- this is a comment
کامنت‌ها با—مشخص می‌شوند.
جمع کردن دو عدد در ورودی:
a = io.read()b = io.read()print(a+b)
توصیف یک پنجره گرافیکی:
d = dialog {hbox {button{ label = "ok" },button{ label = "cancel" }}}
در این مثال dialog با یک جدول توصیف می‌شود.
تابع فاکتوریل نمونه‌ای از یک تابع بازگشی در لوا است، که به دو روش پیاده‌سازی شده‌است:
function factorial(n)
  if n == 0 then
    return 1
  else
    return n * factorial(n - 1)
  endendfunction factorial2(n) -- Shorter equivalent of the above
  return n == 0 and 1 or n * factorial2(n - 1)end
در تابع دوم از خصوصیت محاسبه کمینه عبارت بولی از چپ در زبان لوا استفاده شد. یعنی اینکه فقط اگر شرط n==0 برقرار نباشد محاسبه factorial2(n-1) انجام می‌شود.
عوض کردن تابع print تعریف شده در لوا به گونه‌ای که رفتار آن فقط به ازای حالت خاص ورودی "foo" تفاوت کند.
do
  local oldprint = print           -- Store current print function as oldprint
  function print(s)                -- Redefine print function
    if s == "foo" then
      oldprint("bar")
    else
      oldprint(s)
    end
  endend
این مثال به خوبی نشان می‌دهد که توابع کتابخانه در لوا متغیرهای عمومی (یا سطح اول) هستند.

سیستم تایپ

لوا پویندگی تایپ دارد، یعنی نوع متغیرها در لوا در زمان همگردانی مشخص نمی‌شود، بلکه هر متغیر در زمان اجرا نوعش تعیین می‌شود. در واقع در تعریف متغیر، نوع متغیر توسط برنامه‌نویس نوشته نمی‌شود. زمانی که برنامه‌نویس یک عمل مقداردهی به آن متغیر انجام می‌دهد، نوع متغیر بسته به چیزی که در متغیر ذخیره می‌شود تعیین می‌شود. نوع متغیرها می‌تواند در طول اجرا تغییر کند.

جدول‌ها

داده‌ساختار اولیه لوا، جدول است.
جدول ساختارداده‌ای شبیه آرایه است، اما می‌توان از هر تایپی (عدد، رشته، ...) برای اندیس‌گذاری در آن استفاده کرد. مثلاً در کد زیر:
t = {}t["mohsen"] = 1t["ali"]=2x = t["mohsen"] + t["ali"]print(x) -- prints 3
با جای اندیس‌های عددی از اندیس "mohsen" و "ali" استفاده شده‌است. قبل از اینکه بتوان از عملگر [] استفاده کرد باید متغیر مربوطه را مقدار دهی اولیه به مقداری جدولی کرد. ساده‌ترین مقداردهی اولیه مقدار به {} دادن است. می‌توان از عبارات زیر هم استفاده کرد:
t = {mohsen = 1, ali = 2}
در کد بالا t مانند مثال قبلی مقداردهی می‌شود.
داده‌ساختار جدول (از دید برنامه‌نویس) شبیه جدولی است که دو ستون دارد: در ستون اول تعدادی صفت مشخص شده‌است (مثلاً رنگ، اندازه، ...). در ستون دوم جلوی هر سطر مقدار متناظر آن صفت آمده‌است. مثلاً روبروی سطر رنگ آمده‌است آبی.
برنامه‌نویس می‌تواند یک سطر به جدول اضافه کند، یک سطر را حذف کند یا مقدار ستون دوم یک سطر را تغییر دهد. همهٔ این اعمال با این نحو انجام می‌شود:
t[x] = a
که در آن t یک جدول است و x نام ستون است.
اگر x مقداری بوده که قبلاً هم در جدول بوده (در ستون اول)، آنگاه مقدار روبروی آن x می‌شود. در حالت خاصی که a برابر nil باشد آن ستون حذف می‌شود. اگر x قبلاً در جدول نبوده آنگاه یک سطر جدید اضافه می‌شود که در ستون اول آن x و در ستون دوم a آمده‌است.
پس یک جدول یک رشته را به یک مقدار متناظر می‌کند.
از جدول برای پیاده‌سازی مفاهیم مختلفی استفاده می‌شود: آرایه، شی و فضای نام

جدول به عنوان آرایه

در مثال زیر:
colors1 = {"blue", "green"}colors2[1] = "blue"colors2[2] = "green"
مقداردهی colors1 از نوع جدیدی است که با مقداردهی colors2 معادل است. به این ترتیب یک جدول که اندیس‌هایش اعداد صحیح ۱ تا n هستند می‌تواند بیانگر مفهوم یک آرایه یاشد.

جدول به عنوان داده ساختار شی

هر شی را می‌توان با یک جدول نمایش داد. مثلاً اگر می‌خواهیم اتاقی را توصیف کنیم، می‌توان از کد زیر استفاده کرد:
room = {color="blue",x=10,y=20}
در این مثال صفت‌هایی که برای توصیف به کار رفته‌اند، عبارتند از طول و عرض اتاق (x و y) و رنگ (color).
لوا از تایپ‌دهی اردکی استفاده می‌کند. کلاس‌ها در لوا تعریف نمی‌شوند، فقط نمونه‌ها از اشیا تعریف می‌شوند. در تعریف هرنمونه باید تمامی صفات را
معین کرد.
وفتی روی هر نمونه‌ای عملیات انجام می‌شود، درجا امکان انجام عملیات بررسی می‌شود.
اگر این امکان نباشد خطای زمان اجرا پیش می‌آید (البته می‌توان با فراجدول‌ها بعضی از این خطاها را مهار کرد)
پس در صورتی که نمونه‌ای خصوصیات کافی داشته باشد، از نظر مفهومی به عنوان یک نمونه از یک کلاس خاص محسوب می‌شود.
برای سادگی لوا یک نحو ویژه برای این‌گونه استفاده از جدول ایجاد کرده‌است. با نوشتن room.x در مثال بالا به مقدار x دست می‌یابیم، دقیقاً مانند اینکه نوشته باشیم room["x"]. این نمایش مفهوم شیء را می‌رساند و به کسانی که با نحو object.attribute عادت دارند هم کمک می‌کنند.

سازوکار تولید مثل

برای ساده‌سازی فرایند نمونه‌سازی لوا از سازوکار تولید مثل استفاده می‌کند. در این سازکار، یک نمونه ساخته می‌شود و سپس از تابعی کمکی استفاده می‌شود تا مانند آن نمونه به تعداد کافی ساخته شود. این تابع به صورت زیر قابل پیاده‌سازی است.
function clone(o)local new_o = {}local i,v = next(o,nil)while i donew_o[i] = vi, v = next(o, i)endreturn new_oend
در این مثال از ابزارهای انعطاف پذبر استفاده شده‌است.

جدول به عنوان نام فضای نام

با استفاده از یک جدول می‌توان توابع مرتبط را در یک فضا نگهداری کرد:
Point = {}Point.new = function (x, y)
  return {x = x, y = y}endPoint.set_x = function (point, x)
  point.x = xend

ابزارهای انعطاف‌پذیر

در لوا می‌توان روی عناصر یک جدول حرکت کرد. برای این منظور از تابع next استفاده می‌شود. این تابع یک متغیر می‌گیرد و اولین عضو بعد از آن در جدول را برمی‌گرداند (توجه کنید که اعضا در جدول با ترتیبی تعریف نشده نگهداری می‌شوند). next(nil) اولین عضو جدول را برمی‌گرداند.
این قابلیت به لوا امکانات زیادی می‌دهد از جمله تولید مثل اشیا یا ذخیره‌سازی و بازیابی تمام متغیرهای عمومی.

حوزه متغیرها

حوزه متغیرها در لوا دو حالت دارد: عمومی و محلی. متغیرهای محلی با عبارت local در ابتدای آن‌ها مشخص می‌شوند. متغیرهای عمومی در همه جا قابل دسترسی هستند اما متغیرهای محلی فقط در بدنه همان تابع یا توابع تعریف شده در درون آن تابع قابل دسترسی هستند.
مثال زیر به خوبی حوزه متغیرها را نشان می‌دهد. در تابع print به جای مقادیری که در حوزه نیستند nil چاپ می‌شود.
y = 1function f(x)y = 2local z = 3local function g(r)z = 4endg(z)return zendprint(f(x)) -- prints 4print(y) -- prints 2print(z) -- prints nil

فراجدول‌ها

لوا دارای سیستم تایپ پویا می‌باشد؛ بنابراین در زمان اجرا مشخص می‌شود که انجام یک عمل خاص ممکن است یا نه. مثلاً در کد زیر
a1 = {x=1,y=2}a2 = {x=3,y=4}c = a1 + a2
وقتی که کد مربوط به c=a+b تفسیر می‌شود امکان‌ناپذیر بودن آن مشخص می‌شود. برای اینکه احاطه برنامه‌نویس بر روند اجرا بیشتر شود، قابلیت فراجدول به زبان اضافه شد. این قابلیت اجازه می‌دهد که به جای رخ دادن خطاهای زمان اجرا تابعی از پیش تعیین شده توسط کاربر اجرا شود. این تابع وضعیت خطا را بررسی می‌کند و می‌تواند هم فعالیتی را در این زمان انجام دهد (مثلاً ضبط این موضوع که خطا رخ داده در یک فایل) یا اینکه یک مفهوم جدید برای عمل تعریف نشده تعریف کند. در مثال بالا جمع دو جدول را این‌گونه تعریف کرد که مقادیر متناظر نام‌های یکسان را جمع می‌کنیم (اگر مقداری در جدول اول بود که متناظری در جدول دوم نداشت حاصل nil می‌شود).
کد این فراجدول این‌گونه است:
mt = { __add = function (a, b)local ret={}local i=next(a,nil)while i dov = a[i]if b[i]==nil thenreturn nilendv_p = b[i]e, r = pcall(function() return v + v_p end)if e thenret[i] = relsereturn nilendi = next(a,i)endreturn ret;end}a1 = {x=1,y=2}a2 = {x=3,y=4}setmetatable(a1,mt)c = a1 + a2print(c.x,c.y)
این کد در lua نسخه ۵٫۱ به قبل قابل ترجمه نیست چون آن زمان به جای فراجدول‌ها سازوکار مشابهی به نام عقب‌گردان‌ها (Fallbacks) وچود داشت. عقب‌گردان‌ها به علت مشکلاتی که در خوانایی و قابلیت نگهداری و استفاده دوباره کد ایجاد می‌کردند از نسخه ۳٫۰ به بعد از زبان حذف شدند.

برنامه‌نویسی شیءگرا

شیءگرایی در لوا به‌طور مستقیم وارد نشده‌است. شیءگرایی از طریق سازوکار فراجدول‌ها قابل پیاده‌سازی است. نمونه‌ای ساده از پیاده‌سازی شیءگرایی:
a={x=100, y=200, color="red"}b={x=300, y=400}setmetatable(b,{ __index = a })print(b.color) --> red

رابط برنامه‌نویسی نرم‌افزار (API)

زبان‌های پردازه‌نویسی درون یک زبان بزرگتر اجرا می‌شوند. این زبان باید با زبان پردازه‌نویسی داخلی از طریق یک لایه ارتباط برقرار کند، که آن رابط ربن (برنامه‌نویسی نرم‌افزار) است.
در لوا از دو بخش هسته و کمکی تشکیل شده‌است. لایه ربن در لوا نسبتاً ساده است، چون برخلاف پایتون مدیریت ارجاع در آن نیاز نیست. ربن لوا همچون خود زبان کمینه است: عملکرد پیشرفته‌تر توسط کتاب‌خانهٔ کمکی پشتیبانی می‌شود، که شامل ماکروهای تعریف شده زیادی است که عملیات پیچیده جدولی را آسان می‌کند.