בלוקים של ערכים תואמים לביטויים. כשמשתמשים בבלוק ערך כבלוק פנימי, יכול להיות שתצטרכו להשתמש בביטוי שהוא יוצר יותר מפעם אחת בקוד של הבלוק. לדוגמה, בבלוק שמקבל את האלמנט האחרון ברשימה נעשה שימוש בביטוי שיוצר את הרשימה פעמיים.
// Incorrect block-code generator.
javascriptGenerator.forBlock['last_element'] = function(block, generator) {
// Get the expression that creates the list.
const listCode = generator.valueToCode(block, 'LIST', Order.MEMBER);
// listCode is used twice.
const code = `${listCode}[${listCode}.length - 1]`;
return [code, Order.MEMBER];
}
המצב הזה גורם לבעיות אם הקוד של הבלוק הפנימי יוצר ערכים שונים בכל פעם שהוא מופעל, או אם לקוד יש תופעות לוואי. לדוגמה, אם הקוד של הבלוק הפנימי הוא למעשה קריאה לפונקציה, הקוד הזה יכול לגרום לתנאי יציאה מטווח:
randomList()[randomList().length - 1]
כדי למנוע את הבעיה הזו, הקוד צריך להריץ את הקוד של בלוק פנימי בדיוק פעם אחת. ניתן לעשות זאת בשתי דרכים:
משתנים זמניים: שומרים במטמון את התוצאה של הערכת הקוד של הבלוק הפנימי במשתנה זמני, ומשתמשים במשתנה הזמני במקום זאת. אפשר להשתמש בשיטה הזו רק אם הבלוק הוא בלוק של הצהרה.
פונקציות שירות: יוצרים פונקציה שמבצעת את העבודה שצריך לבצע ומעבירים את התוצאה של הערכת הקוד של הבלוק הפנימי כארגומנט לפונקציה הזו. אפשר להשתמש בשיטה הזו גם בבלוק של ערך וגם בבלוק של משפט.
משתנים זמניים
משתנה זמני מאחסן את הערך של מחרוזת הקוד של בלוק פנימי, כך שהקוד ייבדק רק פעם אחת, ולאחר מכן אפשר יהיה להפנות לערך כמה פעמים.
אי אפשר להשתמש במשתנים זמניים בבלוק ערכים, כי בלוק ערכים חייב להחזיר שורת קוד אחת. במקום זאת, צריך להשתמש בפונקציית שירות.
import {javascriptGenerator, Order} from 'blockly/javascript';
// Correct block-code generator for a statement block that prints the last element of a list.
javascriptGenerator.forBlock['print_last_element'] = function(block, generator) {
// Get the expression that creates the list.
const listCode = generator.valueToCode(block, 'LIST', Order.MEMBER);
// Get the name of a temporary variable.
const listVar = generator.nameDB_.getDistinctName(
'temp_list', Blockly.names.NameType.VARIABLE);
// Evaluate listCode once and assign the result to the temporary variable.
const code = `var ${listVar} = ${listCode};\n`;
// Print the last element of the list.
code += `print(${listVar}[${listVar}.length - 1]);\n`;
return code;
}
לדוגמה, אם הקוד של הבלוק הפנימי הוא קריאת הפונקציה randomList()
, הקוד שנוצר הוא:
var temp_list = randomList();
print(temp_list[temp_list.length - 1]);
קריאה ל-getDistinctName
מקבלת את שם המשתנה הרצוי ומחזירה שם שלא יוצר התנגשויות עם משתנים מוגדרים על ידי משתמשים.
צמצום הקוד המיותר
החיסרון של משתנים זמניים הוא שאם הקוד של הבלוק הפנימי הוא ערך ולא פונקציה או ביטוי, מתקבל קוד מיותר:
// Assigning to temp_list is unnecessary.
var temp_list = foo;
print(temp_list[temp_list.length - 1]);
כדי ליצור קוד נקי יותר, אפשר לבדוק אם הקוד של הבלוק הפנימי הוא ערך, ולכלול את המשתנה הזמני רק אם הוא לא.
if (listCode.match(/^\w+$/)) {
const code = `print(${listCode}[${listCode}.length - 1]);\n`;
} else {
const listVar = generator.nameDB_.getDistinctName(
'temp_list', Blockly.names.NameType.VARIABLE);
const code = `var ${listVar} = ${listCode};\n`;
code += `print(${listVar}[${listVar}.length - 1]);\n`;
}
פונקציות שירות
פונקציית שירות היא פונקציה שהוגדרה על ידי המפתחים, שכלולה במחרוזת הקוד שנוצרה. אפשר להשתמש בהם כדי לוודא שהקוד בתוך הבלוק ייבדק רק פעם אחת, ולאחר מכן אפשר להפנות לערך כמה פעמים.
אפשר להשתמש בפונקציות השירות בבלוק ערכים ובבלוק הצהרה. עם זאת, בדרך כלל צריך להשתמש במשתנים זמניים בבלוק הצהרות, כי הם בדרך כלל קריאים יותר.
import {javascriptGenerator, Order} from 'blockly/javascript';
// Correct block-code generator for a value block that gets the last element of a list.
javascriptGenerator.forBlock['last_element'] = function(block, generator) {
// Get the expression that creates the list.
const listCode = generator.valueToCode(block, 'LIST', Order.NONE);
// Create a function that accepts a list and returns its last element. The
// language generator adds this function to your code.
const functionName = generator.provideFunction_(
'list_lastElement',
[
`function ${generator.FUNCTION_NAME_PLACEHOLDER_}(list) {`,
` return list[list.length - 1];`,
`}`
]
);
// Create an expression that calls the function with listCode as its argument.
// This evaluates listCode once and passes the resulting list to the function.
const code = `${functionName}(${listCode})`;
return [code, Order.FUNCTION_CALL];
}
לדוגמה, אם הקוד של הבלוק הפנימי הוא קריאת הפונקציה randomList()
, הקוד שנוצר הוא:
// This code is added to the overall code returned by the code generator.
function list_lastElement(list) {
return list[list.length - 1];
}
// This code is returned by your inner block.
list_lastElement(randomList());
הוספת הפונקציה
אפשר להגדיר פונקציות שירות בתוך גנרטורים של קוד בלוקים באמצעות provideFunction_
. הפונקציה מקבלת את השם הרצוי לפונקציית השירות ואת מערך של מחרוזות קוד שמכילות את ההגדרה של הפונקציה. הפונקציה מחזירה את השם שנוצר לפונקציית השירות, אחרי (אולי) שינוי כדי שלא יהיה לה סכסוך עם פונקציות שהוגדרו על ידי משתמשים.
provideFunction_
גם מסיר כפילויות מהגדרות של פונקציות שירות, כך שכל פונקציית שירות קיימת רק פעם אחת, גם אם סוג הבלוק שמגדיר אותה קיים כמה פעמים.
עדכון סדרי העדיפויות
כשמגדירים פונקציית שירות, צריך גם לעדכן את סדרי העדיפויות (שמגדירים איך מוסיפים סוגריים) שכלולים בגנרטור של קוד הבלוק.
תמיד יש עדיפות למחרוזת הקוד שמוחזרת על ידי הגנרטור של קוד הבלוק. הוא לא מתייחס לאופרטורים בתוך פונקציות שירות. כך לדוגמה, בקריאה הקודמת של valueToCode
הוחלף הערך ל-Order.NONE
והערך של החזרת ה-tuple השתנה ל-Order.FUNCTION_CALL
.