שפת התכנות מיק-2

במהלך הבנייה בזמן-אמת של תוכנית הלימודים לשיעור "מבוא למדעי המחשב", מצאתי את עצמי מתכנן ומממש שפת תכנות סופר-פרימיטיבית חדשה. בפוסט זה אסביר למה יצרתי אותה ואיך היא עובדת.

אף על פי שאני לא ממש מת על פייתון, ברור לי שכאשר יגיע השלב המתאים – אני מלמד מדעי המחשב בהתנדבות בבית הספר של הילדים שלי – זו תהיה שפת התכנות שאבחר ללמד. היא מאפשרת להגיע לתוצאות מספיק מעניינות בלי לייאש 90% מהתלמידים בדרך, קל מאוד לארגן סביבת פיתוח נוחה עבורה (יתרון חשוב כשהילדים לומדים מהבית) והיא גם רלוונטית לעולם התוכנה המודרני שבחוץ.

יחד עם זאת, היה לי חבל לוותר על תובנות low-level מסוימות, שקשה להגיע אליהן דרך פייתון אבל נראות לי חיוניות כדי להבין ולחוות מחשבים ואלגוריתמים. ללמד שפה נוספת, כמו C או אסמבלי, רק בשביל לסגור את הפינה הזו יהיה כמובן טירוף מוחלט ובזבוז זמן משווע. להסביר את התובנות האלה בעל-פה זה כמו ללמד שחייה בהתכתבות. מה עושים?

וכך, באמצע מקלחת בשתיים בלילה (מכאן השם "מיק-2"), חשבתי פתאום להמציא מעין שפת תכנות מינימליסטית דמוית אסמבלי, מוגבלת ופשוטה במידה קיצונית, שאפשר ללמד במלואה תוך שיעור אחד או פחות, ושכל מטרתה בחיים לתרגל ולהתרגל לאופן שבו מחשבים "חושבים" ועובדים. אחרי מספר איטרציות בראש, החלטתי גם לכתוב מפענח (interpreter) לשפה הזו – הוא ייכתב בפייתון כמובן – כדי שאפשר יהיה להריץ תוכניות ממש ולא רק על הנייר. השפה לא נועדה להיות שימושית לשום דבר חוץ מהתחלת למידה, היא לא נועדה להיראות טוב, ולמי שמבין – אני גם לא בטוח שהיא Turing-complete.

המפרט של מיק-2

כל תוכנית מבוססת על מערך של מספרים שלמים בזיכרון, על משתנה מובנה אחד ויחיד – רגיסטר, ליתר דיוק – ועל סט של ארבע פקודות בלבד. הפקודות הן:

  • RD – העתקה של ערך מאינדקס בזיכרון אל הרגיסטר
  • WR – העתקה של הערך מהרגיסטר אל אינדקס בזיכרון
  • SU – הפחתה של ערך מאינדקס בזיכרון מהערך שברגיסטר
  • JZ – קפיצה לפקודה אחרת אם הערך שברגיסטר הוא אפס

כל תוכנית מתחילה בשורה אחת של אתחול המערך בזיכרון (אנחנו כותבים בה את האינדקסים והערכים שמעניינים אותנו), ואחריה שורות הפקודות. השורות אינן ממוספרות במפורש – המספור מתבסס על סדר הופעתן בקוד, כשהפקודה הראשונה נחשבת פקודה אפס. אפשר להוסיף הערות באמצעות התו "#" (המפענח יתעלם ממנו ומכל מה שאחריו, עד סוף אותה שורה). מספרים נכתבים בייצוג עשרוני רגיל, אינדקסים נכתבים כמספרים מוקפים בסוגריים מרובעים.

מותר לכתוב (WR) לאינדקס במערך שלא אותחל בשורה הראשונה. אם קוראים (RD) מאינדקס במערך שלא צוין קודם לכן, הערך שיוחזר יהיה 0. אם מגיעים או קופצים לפקודה מעבר למספר הפקודות שקיימות בקוד, התוכנית מסתיימת. ואם כותבים ערך לאינדקס [-1] במערך, המפענח ישלח למסך את התו שתואם לערך הזה.

הנה תוכנית לדוגמה:

[0]=1234, [5]=33
RD [0]
WR [1]
RD [5]
WR [-1]

השורה הראשונה מגדירה שבאינדקס [0] במערך המספרים יהיה הערך 1234, ובאינדקס [5] יהיה הערך 33. הפקודה הראשונה (RD) קוראת לרגיסטר את הערך מ-[0], הפקודה הבאה מעתיקה את הערך מהרגיסטר לאינדקס [1] במערך, וזוג הפקודות הבאות בעצם מציג כפלט את התו 33 – סימן קריאה ב-ASCII – כדי לומר לנו, נניח, שהפעולה התבצעה. כלומר, התוכנית כולה בעצם מעתיקה ערך ממקום אחד בזיכרון למקום אחר, ומציגה משוב בסיסי מאוד.

דוגמאות מתקדמות קצת יותר

נניח שאנחנו רוצים תוכנית שתכתוב לאינדקס [1] מספר שגדול ב-1 מהערך שבאינדקס [0]. הרעיון החינוכי כאן הוא שהתלמידים יגידו, קודם כל, "בלתי אפשרי – הרי אין פקודת חיבור, רק פקודת חיסור" – אבל אחרי קצת מחשבה הם יעלו על התובנה המתמטית הבסיסית, שחיבור זה כמו חיסור של מספר שלילי. ואז:

[0]=1234,[2]=-1
RD [0]
SU [2]
WR [1]

ועל בסיס אותה תובנה, רק עם מימוש טיפה יותר מורכב, תוכנית שמחברת שני ערכים (נניח מאינדקסים [0] ו-[1]) ושמה את התוצאה באינדקס [2]:

[0]=123,[1]=456
RD [999]
SU [0]
WR [999]
RD [1]
SU [999]
WR [2]

בשלב הבא, הם יצטרכו לכתוב תוכנית שבודקת אם הערכים בשני אינדקסים במערך הם זהים או שונים. כאן, כיוון שיש שתי אפשרויות לתשובה, נצטרך להיעזר בפקודת הקפיצה המותנית JZ – ולגמרי במקרה, התנאי שהיא בודקת מסתדר לנו יפה עם המתמטיקה של המשימה:

[0]=123,[1]=124,[20]=83,[21]=68
RD [0] # This is line 0
SU [1]
JZ 5
RD [21]
WR [20]
RD [20] # This is line 5
WR [-1] 

יש כמה דרכים לבצע את המשימה הזו, וכמובן צריך להריץ את התוכנית לפחות פעמיים, פעם כשהמספרים ב-[0] וב-[1] זהים ופעם כשהם שונים, כדי לראות שהיא עובדת נכון בשני המצבים. במימוש לעיל, הגדרתי כברירת מחדל את התשובה 83 (התו "S", מייצג Same) בכתובת [20]. מחסירים מספר אחד מהשני. אם התוצאה היא 0 (כלומר הם שווים), התוכנה קופצת לשורה 5 – קריאה של הערך ב-[20] וכתיבתו למסך. אם התוצאה שונה מ-0, המפענח ימשיך לפקודה שמייד אחרי JZ – העתקה של התשובה האחרת, 68 (D ל-Different) לכתובת [20], ואז זה מה שיוצג על המסך.

בוודאי שמתם לב שהפרמטר לפקודת JZ לא מוקף בסוגריים מרובעים. זה כי מדובר בערך בפני עצמו, לא באינדקס. אינדקס לא יתקבל בפקודה הזו, וערכים ללא סוגריים מרובעים לא יתקבלו בפקודות האחרות. נכון, אפשר היה להוסיף את האופציות האלה לשפה בלי קושי, אבל שוב – הרעיון הוא פשטות מצד אחד, ולהכיר קצת סטנדרטים של כתיבת קוד מצד שני.

ועכשיו אתם: מה עושה התוכנית הזו?

[0]=57,[1]=47,[2]=1
RD [0]
WR [-1]
SU [2]
WR [0]
SU [1]
JZ 999
RD [10]
JZ 0

ומה הלאה

השפה הקטנה והמאולתרת הזו לא אמורה להיות בשימוש זמן רב. אם הילדים ישתפו פעולה, הם יכולים להפיק את כל מה שאני מקווה שיפיקו ממנה תוך שני שיעורים, בערך. עם זאת, בשביל התירגול שלי-עצמי וכדי לוודא שאני לא מפדח את עצמי עם קוד שגוי בשפה שאני המצאתי, כתבתי את המפענח בפייתון. כדי להשתמש בו – ואני לא אחראי לתוצאות – צריך לכתוב קובץ קוד מיק-2 בנפרד, ולכתוב את שם קובץ הקוד הזה במשתנה PROGRAM_FILE בתחילת קוד המפענח.

טבילת האש של השפה החדשה תתרחש לקראת סוף השבוע. אם יהיו לי תובנות חדשות מההתנסות בשטח, אכתוב עליהן בפוסט נפרד. יש לכם רעיונות לתוכניות קצרות ומעניינות נוספות?

להרשמה
הודע לי על
7 תגובות
מהכי חדשה
מהכי ישנה לפי הצבעות
Inline Feedbacks
הראה את כל התגובות

אז אתה מגדל את הדור החדש של מתכנתי מכונות טיורינג?
(או BF)

השפה עברה אצל הילדים בשלום?
לדעתי הוספת אנימציה של פעולת ה"מעבד" על מרחב הזכרון שלך בהחלט יכולה להוסיף לילדים עניין והבנה (אבל זה מסבך לך את העבודה מאוד).
בנוסף, אני מבין ש-999 היא הכתובת האחרונה במרחב הזיכרון?

בהצלחה מניסיון שלי קשה מאוד לעניין ילדים במשהו כזה אולי שהקומפילר ידפיס משהו בהתאם למה שהם מתכנתים אולי מכונת מצבים פשוטה

תעדכן איך היה, העברתי כמה קורסי ארדואינו כיתות ז-ט