مولدهای بلاک کد

مولد کد بلوکی تابعی است که کد یک بلوک را تولید کرده و آن را به صورت رشته برمی‌گرداند. اینکه یک بلوک چه کدی تولید می‌کند به نوع آن بستگی دارد:

  • بلوک‌های مقداری دارای یک اتصال خروجی هستند. این بلوک‌ها مانند عبارات در یک زبان مبتنی بر متن عمل می‌کنند و رشته‌هایی تولید می‌کنند که شامل عبارات هستند.
  • بلوک‌های دستوری، بلوک‌هایی بدون اتصال خروجی هستند. این بلوک‌ها مانند دستورات در یک زبان مبتنی بر متن عمل می‌کنند و رشته‌هایی تولید می‌کنند که حاوی دستورات هستند.

نحوه نوشتن یک مولد کد بلوکی

برای هر بلوک سفارشی که ایجاد می‌کنید، باید یک مولد کد بلوک برای هر زبانی که می‌خواهید پشتیبانی کنید، بنویسید. توجه داشته باشید که همه مولدهای کد بلوک با جاوا اسکریپت نوشته شده‌اند، حتی اگر کد را به زبان دیگری تولید کنند.

همه مولدهای کد بلوکی مراحل زیر را انجام می‌دهند:

  1. یک مولد کد زبان وارد کنید.
  2. مقدار هر فیلد را دریافت کرده و آن را به یک رشته کد تبدیل کنید.
  3. رشته‌های کد تولید شده توسط بلوک‌های داخلی را دریافت کنید، که بلوک‌هایی هستند که به ورودی‌های مقدار و عبارت متصل شده‌اند.
  4. رشته کد بلوک را ساخته و برمی‌گرداند.

بلوک‌های نمونه

به عنوان مثال، ما مولدهای کد جاوا اسکریپت را برای بلوک‌های زیر خواهیم نوشت.

  • custom_compare یک بلوک مقدار است که دارای یک ورودی مقدار به نام LEFT ، یک فیلد کشویی به نام OPERATOR و یک فیلد عددی به نام RIGHT است. این بلوک یک رشته عبارت به شکل '0 = 0' تولید می‌کند.

    بلوک مقادیر سفارشی برای مقایسه‌ها.

  • custom_if یک بلوک دستور است که دارای یک فیلد چک‌باکس به نام NOT ، یک ورودی مقدار به نام CONDITION و یک ورودی دستور به نام THEN است. این بلوک یک رشته دستور به شکل 'if (...) {\n...\n};\n' تولید می‌کند.

    بلوک دستور سفارشی برای دستور if. کاربران می‌توانند از یک کادر انتخاب برای منفی کردن شرط if استفاده کنند.

این سند، ژنراتورها را گام به گام می‌سازد. می‌توانید ژنراتورهای تکمیل‌شده را در انتهای این سند پیدا کنید.

توجه داشته باشید که این بلوک‌ها فقط برای نشان دادن تولید کد در نظر گرفته شده‌اند. در یک برنامه واقعی، از بلوک‌های داخلی logic_compare و controls_if استفاده کنید.

وارد کردن یک مولد کد زبان

شما می‌توانید با استفاده از یکی از روش‌های زیر، یک مولد کد زبان را وارد کنید. از مولد وارد شده برای ذخیره مولدهای کد بلوک در شیء forBlock استفاده کنید.

ماژول‌ها

import {javascriptGenerator} from 'blockly/javascript';
import {pythonGenerator} from 'blockly/python';
import {phpGenerator} from 'blockly/php';
import {luaGenerator} from 'blockly/lua';
import {dartGenerator} from 'blockly/dart';

// Add block-code generators for the custom_if block.
javascriptGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
pythonGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
phpGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
luaGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
dartGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };

باز کردن

شما باید ژنراتور را بعد از اینکه Blockly را اضافه کردید، اضافه کنید.

<script src="https://unpkg.com/blockly"></script>
<script src="https://unpkg.com/blockly/javascript_compressed"></script>
<script src="https://unpkg.com/blockly/python_compressed"></script>
<script src="https://unpkg.com/blockly/php_compressed"></script>
<script src="https://unpkg.com/blockly/lua_compressed"></script>
<script src="https://unpkg.com/blockly/dart_compressed"></script>
// Add block-code generators for the custom_if block.
javascript.javascriptGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
python.pythonGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
php.phpGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
lua.luaGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
dart.dartGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };

اسکریپت‌های محلی

شما باید ژنراتور را بعد از اینکه Blockly را اضافه کردید، اضافه کنید.

<script src="blockly_compressed.js"></script>
<script src="javascript_compressed.js"></script>
<script src="python_compressed.js"></script>
<script src="php_compressed.js"></script>
<script src="lua_compressed.js"></script>
<script src="dart_compressed.js"></script>
// Add block-code generators for the custom_if block.
javascript.javascriptGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
python.pythonGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
php.phpGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
lua.luaGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
dart.dartGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };

دریافت مقادیر فیلدها

فیلدها به کاربران اجازه می‌دهند مقادیری مانند رشته‌ها، اعداد و رنگ‌ها را وارد کنند. برای دریافت مقدار یک فیلد، getFieldValue را فراخوانی کنید. آنچه بازگردانده می‌شود از فیلدی به فیلد دیگر متفاوت است. برای مثال، فیلدهای متنی دقیقاً متن وارد شده توسط کاربر را برمی‌گردانند، اما فیلدهای کشویی یک رشته بی‌طرف از نظر زبان مرتبط با موردی که کاربر انتخاب کرده است را برمی‌گردانند. برای اطلاعات بیشتر، به مستندات مربوط به فیلدهای داخلی مراجعه کنید.

بسته به فیلد، ممکن است لازم باشد قبل از استفاده از مقدار برگشتی در کد، آن را تغییر شکل دهید.

custom_compare

javascriptGenerator.forBlock['custom_compare'] = function (block, generator) {
  // Use the value of the OPERATOR dropdown to look up the actual operator.
  const OPERATORS = {
    EQUALS: '==',
    LESS: '<',
    GREATER: '>',
  };
  const operator = OPERATORS[block.getFieldValue('OPERATOR')];
  // The value of the RIGHT field is a number and can be used directly when
  // building the block's code string.
  const right = block.getFieldValue('RIGHT');
  ...
}

custom_if

javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
  // Use the value of the NOT field to get the negation operator (if any).
  const checkbox = block.getFieldValue('NOT');
  const negate = checkbox === 'TRUE' ? '!' : '';
  ...
}

برای اطلاعات بیشتر، به تبدیل مقادیر فیلد مراجعه کنید.

دریافت کد از بلوک‌های داخلی

بلوک‌های داخلی، بلوک‌هایی هستند که به ورودی‌های مقدار و دستور یک بلوک متصل شده‌اند. برای مثال، بلوک custom_if دارای یک بلوک داخلی مقدار برای شرط if و بلوک‌های داخلی دستور برای کدی است که در صورت درست بودن شرط اجرا می‌شود.

برخلاف مقادیر فیلد، کدی که از بلوک‌های داخلی دریافت می‌کنید آماده استفاده است و نیازی به تبدیل ندارد.

بلوک‌های مقدار داخلی

برای دریافت کد از یک بلوک داخلی متصل به یک ورودی مقدار، valueToCode را فراخوانی کنید. این متد مولد کد بلوک داخلی را فراخوانی می‌کند.

custom_compare

import {javascriptGenerator, Order} from 'blockly/javascript';

javascriptGenerator.forBlock['custom_compare'] = function (block, generator) {
  ...
  const order = operator === '==' ? Order.EQUALITY : Order.RELATIONAL;
  const left = generator.valueToCode(block, 'LEFT', order);
  ...
}

custom_if

import {javascriptGenerator, Order} from 'blockly/javascript';

javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
  ...
  const order = checkbox === 'TRUE' ? Order.LOGICAL_NOT : Order.NONE;
  const condition = generator.valueToCode(block, 'CONDITION', order) || 'false';
  ...
}

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

برای مثال، علامت زدن کادر NOT در custom_if یک عملگر منطقی not ( ! ) را به شرط اعمال می‌کند. در این حالت، شما اولویت عملگر not ( Order.LOGICAL_NOT ) را به valueToCode ارسال می‌کنید و valueToCode این را با اولویت ضعیف‌ترین عملگر در بلوک داخلی مقایسه می‌کند. سپس کد بلوک داخلی را در صورت نیاز در خود جای می‌دهد:

  • اگر CONDITION یک بلوک متغیر باشد، valueToCode پرانتز اضافه نمی‌کند زیرا عملگر not می‌تواند مستقیماً روی یک متغیر ( !myBoolean ) اعمال شود.
  • اگر CONDITION یک بلوک مقایسه باشد، valueToCode پرانتز اضافه می‌کند تا عملگر not به کل مقایسه اعمال شود ( !(a < b) ) به جای مقدار سمت چپ ( !a < b ).

در واقع لازم نیست بدانید که آیا valueToCode پرانتز اضافه کرده است یا خیر. تنها کاری که باید انجام دهید این است که اولویت را به valueToCode ارسال کنید و کد برگشتی را به رشته کد خود اضافه کنید. برای اطلاعات بیشتر، به اولویت valueToCode مراجعه کنید.

بلوک‌های دستوری داخلی

برای دریافت کد از یک بلوک داخلی متصل به ورودی یک دستور، تابع statementToCode را فراخوانی کنید. این متد، مولد کد بلوک داخلی را فراخوانی کرده و کد تورفتگی‌دار را مدیریت می‌کند.

custom_compare

بلوک custom_compare هیچ ورودی دستوری ندارد.

custom_if

javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
  ...
  const statements = generator.statementToCode(block, 'THEN');
  ...
}

شما فقط باید برای بلوک داخلی که مستقیماً به ورودی یک دستور متصل است، تابع statementToCode فراخوانی کنید. statementToCode هر بلوک اضافی متصل به بلوک اول را مدیریت می‌کند.

رشته کد خود را بسازید و برگردانید

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

  • بلوک‌های مقداری: آرایه‌ای شامل رشته کد و تقدم ضعیف‌ترین عملگر در کد شما را برمی‌گرداند. valueToCode از این برای تصمیم‌گیری در مورد اینکه آیا کد شما هنگام استفاده از بلوک به عنوان بلوک داخلی، نیاز به قرار گرفتن در پرانتز دارد یا خیر، استفاده می‌کند. برای اطلاعات بیشتر، به Return priority مراجعه کنید.

  • بلوک‌های دستور: رشته کد را برمی‌گرداند.

custom_compare

import {javascriptGenerator, Order} from 'blockly/javascript';

javascriptGenerator.forBlock['custom_compare'] = function (block, generator) {
  ...
  const order = operator === '==' ? Order.EQUALITY : Order.RELATIONAL;
  ...
  const code = left + ' ' + operator + ' ' + right;
  return [code, order];
}

custom_if

javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
  ...
  const code = 'if (' + negate + condition + ') {\n' + statements + '}\n';
  return code;
}

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

مولدهای کد کامل

برای مرجع، در اینجا مولدهای کد کامل برای هر بلوک آورده شده است:

custom_compare

import {javascriptGenerator, Order} from 'blockly/javascript';

javascriptGenerator.forBlock['custom_compare'] = function (block, generator) {
  const OPERATORS = {
    EQUALS: '==',
    LESS: '<',
    GREATER: '>',
  };
  const operator = OPERATORS[block.getFieldValue('OPERATOR')];
  const order = operator === '==' ? Order.EQUALITY : Order.RELATIONAL;
  const left = generator.valueToCode(block, 'LEFT', order);
  const right = block.getFieldValue('RIGHT');
  const code = left + ' ' + operator + ' ' + right;
  return [code, order];
}

custom_if

import {javascriptGenerator, Order} from 'blockly/javascript';

javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
  const checkbox = block.getFieldValue('NOT');
  const negate = checkbox === 'TRUE' ? '!' : '';
  const order = checkbox === 'TRUE' ? Order.LOGICAL_NOT : Order.NONE;
  const condition = generator.valueToCode(block, 'CONDITION', order) || 'false';
  const statements = generator.statementToCode(block, 'THEN');
  const code = 'if (' + negate + condition + ') {\n' + statements + '}\n';
  return code;
}