آيكون (لغة برمجة)

الصيغة الأساسية

لغة آيكون مشتقة من فئة ALGOL من لغات البرمجة المهيكلة، ولهذا فهي لها صيغة تشبه لغة سي أو لغة باسكال. وتشبه لغة آيكون إلى حد كبير لغة باسكال، حيث تستخدم صيغة := للتعيينات، والكلمة الأساسية procedure وصيغة مماثلة. ومن ناحية أخرى، تستخدم لغة آيكون الأقواس التي تأخذ شكل حرف C لبناء مجموعات التنفيذ، وتبدأ البرامج بتشغيل إجراء يُطلق عليه "main".
تتشارك لغة آيكون أيضًا بطرق عديدة الميزات مع معظم لغات البرمجة النصية (بالإضافة إلى لغات سنوبول وSL5، والتي أُخذت منها): والمتغيرات ليست هناك حاجة لإظهارها، والأنواع يتم تشكيلها تلقائيًا، والأرقام يمكن تحويلها إلى سلاسل وإعادتها إلى الأرقام تلقائيًا. وهناك ميزة أخرى مشتركة مع العديد من لغات البرمجة النصية، لكن ليست كلها، وهي نقص رمز نهاية السطر؛ ففي لغة آيكون، الأسطر التي لا تنتهي بفاصلة منقوطة تنتهي بفاصلة منقوطة ضمنية.
الإجراءات هي وحدة البناء الأساسية لبرامج آيكون، وعلى الرغم أنها تستخدم تسمية باسكال إلا أنها تعمل بشكل يشبه دوال C إلى حد كبير ويمكن أن تعطي قيم؛ لا توجد الكلمة الأساسية function في لغة آيكون.
procedure doSomething(aString)
  write(aString)
end

التنفيذ الموجه بالأهداف

أحد المفاهيم الأساسية للغة Icon يتمثل في أن هياكل التحكم تستند إلى "نجاح" أو "فشل" التعبيرات، بدلاً من المنطق البولي، كما هو في معظم لغات البرمجة. وفي هذا النموذج، المقارنات البسيطة مثل if a <b لا تعني "إذا قُيّمت العمليات على اليمين بالصحيحة" حيث ستكون في معظم اللغات؛ لكنها بدلاً من ذلك تعني شيءًا أقرب إلى "إذا كانت العمليات على اليمين ناجحة". وفي هذه الحالة ينجح العامل <إذا كانت المقارنة صحيحة، وبهذا تكون النتيجة النهائية هي نفسها. إضافة إلى هذا، يُعيد العامل <وسيطته الثانية إذا نجحت، ما يسمح بأشياء مثل if a <b <c، وهو نوع شائع من المقارنة لا يمكن وضعه مباشرة في معظم لغات البرمجة.
تصبح فائدة هذا المفهوم أكثر وضوحًا عندما تضع في اعتبارك أمثلة من العالم الواقعي. ونظرًا لأن لغة Icon تستخدم النجاح أو الفشل لكافة عمليات التحكم في التدفق، فإن هذه التعليمة البرمجية البسيطة:

if a := read() then write(a)
ستنسخ سطرًا واحدًا من الإدخال القياسي إلى الإخراج القياسي. والمهم في هذا المثال هو أن التعليمة البرمجية ستعمل حتى إذا تسبب الأمر read() في حدوث خطأ، على سبيل المثال، إذا لم يوجد الملف. وفي تلك الحالة ستفشل العبارة a := read()، ولن يُطلب أمر الكتابة.
يسري النجاح والفشل فيما بين الدوال، ما يعني أن أي فشل داخل أي دالة متداخلة سيتسبب في فشل باقي الدوال التي تتداخل معها أيضًا. على سبيل المثال، يمكننا كتابة برنامج لنسخ ملف إدخال كامل إلى إخراج في سطر واحد:

while write(read())
عندما يفشل الأمر read()، في نهاية الملف على سبيل المثال، سيسري الفشل في السلسلة وسيفشل الأمر write() أيضًا. وصيغة while، بوصفها هيكل تحكم، تتوقف عند الفشل، أي أنها تتوقف عندما يكون الملف فارغًا. وللمقارنة، ادرس مثالاً مشابهًا مكتوبًا بخوارزمية pseudocode المستندة إلى لغة جافا Java:
try {
  while ((a = read()) != EOF) {
    write(a);
  }
} catch (Exception e) {
  // do nothing, exit the loop
}
في هذه الحالة هناك مقارنتان مطلوبتان، واحدة لنهاية الملف (EOF) والأخرى لكافة الأخطاء الأخرى. ونظرًا لأن لغة Java لا تسمح بمقارنة الأخطاء كعناصر منطقية، كما هو الحال في لغة Icon، فيجب استخدام صيغة try/catch المطولة كبديل. كما أن محاولة استخدام الكتل تفرض تأثيرات سلبية على الأداء لمجرد استخدامها، حتى إذا لم تحدث أي أخطاء، وهي تكلفة موزعة تتجنبها لغة Icon.
تشير لغة Icon إلى هذا المفهوم بأنه تنفيذ موجه بالأهداف، حيث يشير إلى الطريقة التي يستمر بها التنفيذ حتى يتحقق هدف ما. وفي المثال السابق، يتمثل الهدف في قراءة الملف بكامله؛ ويتواصل نجاح أمر القراءة طالما أن هناك المزيد من المعلومات المطلوب قراءتها، بينما يفشل عندما لا تكون هناك معلومات. وهكذا تتم برمجة الهدف مباشرة في اللغة، بدلاً من استخدام عبارات لفحص التعليمات البرمجية العائدة أو البنيات المماثلة.

المولدات

كثيرًا ما تعطي التعبيرات في لغة Icon قيمة واحدة، على سبيل المثال، x <5 ستُقيّم وتنجح مع القيمة 5 أو تفشل. ومع ذلك، يعتمد العديد من الأمثلة التالية على حقيقة أن هناك تعبيرات عديدة لا تعطي نتيجة بالنجاح أو الفشل فورًا، حيث تعطي قيمًا في أثناء ذلك. وهذا يسري على الأمثلة ذات الصيغ every and to؛ every causes to لتواصل إعطاء قيم حتى تفشل.
هذا مفهوم رئيسي في لغة Icon، يُعرف بـ المولدات. وتؤدي المولدات كثير من وظائف الحلقة في اللغة، لكنها تفعل الكثير مباشرة؛ لا يكتب المُبرمج أي حلقة ثم يقوم بسحب القيم ومقارنتها، لكن لغة Icon ستفعل كل هذا لك.
داخل طريقة تعبير لغة Icon، يؤدي تقييم أي تعبير أو دالة إلى تسلسل نتائج. ويحتوي تسلسل النتائج على كل القيم الممكنة التي يمكن توليدها بواسطة التعبير أو الدالة. وعندما ينتهي تسلسل النتائج (على سبيل المثال، لا يوجد مزيد من القيم داخل تسلسل النتائج)، يفشل التعبير أو الدالة. ويتحقق التكرار في تسلسل النتائج إما ضمنيًا عبر التقييم الموجه بالأهداف في Icon أو صراحة عبر العبارة every.
تشتمل لغة Icon على العديد من مُنشئات المولدات. وتسمح الصيغة alternator بتوليد سلسلة من العناصر في تسلسل حتى يفشل أحدها:
1 | "hello" | x <5 
يمكنه توليد "1" و"hello" و"5" إذا كان x أقل من 5. على سبيل المثال، يمكن قراءة Alternators كـ "or" في حالات عديدة:
if y <(x | 5) then write("y=", y)
سيكتب قيمة y إذا كانت أصغر من x أو 5. وداخليًا تقوم لغة Icon بفحص كل قيمة من اليسار إلى اليمين حتى ينجح أحدها أو تفرغ القائمة وتفشل. وتذكر أنه لن تُطلب الدوال إلا إذا لم تفشل الطلبات بالداخل، ولذلك يمكن تقصير هذا المثال إلى:
write("y=", (x | 5)> y)
هناك مولد بسيط آخر وهو to، حيث يقوم بتوليد قوائم من الأعداد الصحيحة؛ وسيقوم every write(1 to 10) بعمل ما يبدو عليه تمامًا. وستقوم الصيغة bang بتوليد كل عنصر قائمة؛ وسيقوم every write(!aString) بإخراج كل رمز في سلسلة في سطر جديد.
لإيضاح قوة هذا المفهوم، انظر في عمليات السلاسل. وتشتمل معظم اللغات على دالة تُعرف بـ find أو indexOf والتي تعطي موقع سلسلة داخل أخرى. ادرس:
s = "All the world's a stage. And all the men and women merely players";
i = indexOf("the", s)
ستعطي هذه التعليمة البرمجية 4، موضع الحالة الأولى للكلمة "the". وللحصول على المثال التالي للكلمة "the" يجب استخدام شكل بديل،
i = indexOf("the", s, 5)
الرقم 5 في النهاية يقول أنه ينبغي أن يبدو من الموضع 5 وما بعده. ولاستخراج كل حالات كلمة "the"، يجب استخدام حلقة...
s = "All the world's a stage. And all the men and women merely players";
i = indexOf("the", s)
while i != -1 {
  write(i);
  i =  indexOf("the", s, i+1);
}
في لغة Icon تمثل الدالة find مولدًا، وستعطي المثال التالي من السلسلة عند كل استئناف لها قبل أن تفشل في النهاية بعد تجاوزها نهاية السلسلة. ويمكن كتابة نفس التعليمة البرمجية في لغة Icon:
s := "All the world's a stage. And all the men and women merely players"
every write(find("the",s))
ستعطي الدالة find فهرس المثال التالي لكلمة "the" عند كل استئناف لها بواسطة العبارة every، حيث في النهاية تتجاوز نهاية السلسلة وتفشل. وكما في المثال السابق، سيؤدي هذا إلى فشل الكتابة، وخروج حلقة every (أحادية السطر).
هناك بالطبع أوقات تريد فيها عمدًا البحث عن سلسلة بعد نقطة ما في إدخال، على سبيل المثال، قد تكون تفحص ملفًا نصيًا يحتوي على بيانات في أعمدة متعددة. ويصلح التنفيذ الموجه بالأهداف هنا أيضًا، ويمكن استخدامه بهذه الطريقة:
write(5 <find("the", s))
سيظهر الموضع فقط إذا ظهرت كلمة "the" بعد الموضع 5، وإلا فستفشل المقارنة، ممررة ذلك الفشل إلى write() كما سبق. يوجد "حيلة" واحدة صغيرة لهذه التعليمة البرمجية والتي تحتاج لوضعها في الاعتبار: تعطي المقارنات النتيجة اليمنى، لذلك من المهم وضع find على الجانب الأيمن من المقارنة. وإذا كان الرقم 5 على اليمين، فسيُكتب الرقم 5.
تضيف لغة Icon العديد من هياكل التحكم للتكرار الحلقي خلال المولدات. وعامل every يشبه while، ويحدث التكرار الحلقي خلال كل عنصر بواسطة مولد ويخرج عند الفشل:

 every k := i to j do
  write(someFunction(k))
لماذا استخدام every بدلاً من حلقة while في هذه الحالة؟ لأن while تعيد تقييم النتيجة الأولى، لكن every تقدم كل النتائج. وتقوم صيغة every فعليًا بإدخال قيم في الدالة بطريقة تشبه الكتل في لغة Smalltalk. على سبيل المثال، يمكن إعادة كتابة الحلقة السابقة بهذه الطريقة:
every write(someFunction(i to j))
بإمكان المستخدمين إنشاء مولدات جديدة بسهولة باستخدام الكلمة الأساسية suspend:
procedure findOnlyOdd(pattern, theString)
  every i := find(pattern, theString) do
    if i % 2 = 1 then suspend i
end
هذا المثال يتحلق خلال theString باستخدام find للبحث عن pattern. وعند العثور على واحد، وكان الموضع غريبًا، تعطي الدالة الموقع مع suspend. وبخلاف return، يقوم suspend بكتابة أين يوجد في المولدات الداخلية أيضًا، ما يتيح له معرفة أين تُرك في التكرار التالي.

السلاسل

مع وظائفها التي تشبه البرمجة النصية، تضيف لغة Icon عددًا من الميزات لجعل العمل مع السلاسل أسهل. والأبرز بين هذه الميزات نظام scanning، والذي يكرر طلب الدوال في أي سلسلة:
s ? write(find("the"))
هي شكل قصير من الأمثلة الموضحة في السابق. وفي هذه الحالة يُوضع موضوع الدالة find خارج المعلمات أمام علامة الاستفهام. وتُكتب دوال Icon عمدًا (على عكس تلقائيًا) لمعرفة الموضوع في قوائم المعلمات والسماح بسحبها بهذه الطريقة.
يمكن استخراج السلاسل الفرعية من أي سلسلة باستخدام مواصفات نطاق داخل الأقواس. ويمكن أن تعطي مواصفات النطاق نقطة لرمز مفرد، أو شريحة من السلسلة. ويمكن فهرسة السلاسل من اليمين أو اليسار. ومن المهم ملاحظة أن المواضع داخل أي سلسلة تكون بين الرموز 1A2B3C4 ويمكن تحديدها من اليمين -3A-2B-1C0
على سبيل المثال
"Wikipedia"[1]   ==> "W"
"Wikipedia"[3]   ==> "k"
"Wikipedia"[0]   ==> "a"
"Wikipedia"[1:3] ==> "Wi"
"Wikipedia"[-2:0] ==> "ia"
"Wikipedia"[2+:3] ==> "iki"
يوضح المثال الأخير استخدام طول بدلاً من موضع إنهاء
يمكن استخدام مواصفات النصوص المنخفضة كقيمة Lvalue داخل تعبير. وهذا يمكن استخدامه لإدراج سلاسل في سلسلة أخرى أو حذف أجزاء من سلسلة. على سبيل المثال،
   s := "abc"
   s[2] := "123"   
   s now has a value of "a123c"
   s := "abcdefg"
   s[3:5] := "ABCD"  
   s now has a value of "abABCDefg"
   s := "abcdefg"
   s[3:5] := ""
   s now has a value of "abefg"

هياكل أخرى

تسمح أيضًا لغة Icon للمستخدم بإنشاء قوائمه الخاصة بسهولة (أو المصفوفات):
aCat := ["muffins", "tabby", 2002, 8]
ويمكن أن تكون العناصر داخل أي قائمة من أي نوع، وتشمل هياكل أخرى. ولإنشاء قوائم أكبر بسرعة، تشتمل لغة Icon على مولد list؛ i := list(10, "word") تولد قائمة تحتوي على 10 من كلمة "word".
ومثل المصفوفات في اللغات الأخرى، تسمح لغة Icon بالبحث عن العناصر حسب الموضع، على سبيل المثال، weight := aCat[4].
صيغة bang-syntax، على سبيل المثال، every write(!aCat)، ستطبع أربعة أسطر، بكل منها عنصر واحد.
وتشتمل لغة Icon على دوال تشبه التكديس، push وpop للسماح لها بتشكيل أساس التكديسات وقوائم الانتظار.
تشتمل لغة Icon أيضًا على وظائف للمجموعات والجداول (تُعرف بـ hashes وassociative arrays وdictionaries، إلخ.):
symbols := table(0)
symbols["there"] := 1
symbols["here"] := 2
تقوم هذه التعليمة البرمجية بإنشاء جدول سيستخدم الصفر كقيمة افتراضية لأي مفتاح غير معروف. ثم تضيف عنصرين إليه، بالمفاتيح "there" و"here"، والقيم 1 و2.

فحص السلاسل

تتمثل إحدى الميزات القوية للغة Icon في فحص السلاسل. وعامل فحص السلاسل، ? يحفظ بيئة فحص السلاسل الحالية وينشئ بيئة فحص سلاسل جديدة. تتكون بيئة فحص السلاسل من متغيري كلمات أساسية، &subject و&pos. حيث يمثل &subject السلسلة التي يجري فحصها، بينما &pos يمثل المؤشر أو الموضع الحالي داخل سلسلة subject.
على سبيل المثال

 s := "this is a string"
 s ? write("subject=[",&subject,"] pos=[",&pos,"]")
ستنتج

subject=[this is a string] pos=[1]

يمكن استخدام الدوال المضمنة والمعرفة من قبل المستخدم للتحرك داخل السلسلة التي يجري فحصها. والعديد من الدوال المضمنة ستكون افتراضيًا &subject و&pos (على سبيل المثال دالة find). والتعليمات التالية، على سبيل المثال، ستكتب كل "الكلمات" المحددة الفارغة في سلسلة.
 s := "this is a string"
 s ? {                               # Establish string scanning environment 
     while not pos(0) do  {          # Test for end of string 
         tab(many(' '))              # Skip past any blanks
         word := tab(upto(' ') | 0)  # the next word is up to the next blank -or- the end of the line
         write(word)                 # write the word
     }
  }
مثال أكثر تعقيدًا يوضح دمج المولدات وفحص السلاسل داخل اللغة.
procedure main()
    s := "Mon Dec 8"
    s ? write(Mdate() | "not a valid date")
end
# Define a matching function that returns
# a string that matches a day month dayofmonth
procedure Mdate()
# Define some initial values
static dates
static days
initial {
       days := ["Mon","Tue","Wed","Thr","Fri","Sat","Sun"]
       dates := ["Jan","Feb","Mar","Apr","May","Jun",
                 "Jul","Aug","Sep","Oct","Nov","Dec"]
}
every suspend   (retval <-  tab(match(!days)) ||     # Match a day
                            =" " ||                  # Followed by a blank
                            tab(match(!dates)) ||    # Followed by the month
                            =" " ||                  # Followed by a blank
                            matchdigits(2)           # Followed by at least 2 digits 
                ) &
                (=" " | pos(0) ) &                   # Either a blank or the end of the string
                retval                               # And finally return the string
end
# Matching function that returns a string of n digits
procedure matchdigits(n)
    suspend (v := tab(many(&digits)) & *v <= n) & v
end

مصطلح expr1 & expr2 & expr3 يعطي قيمة التعبير الأخير