Trình tạo mã khối

Trình tạo mã khối là một hàm tạo mã cho một khối và trả về mã đó dưới dạng một chuỗi. Mã mà một khối tạo ra phụ thuộc vào loại khối đó:

  • Khối giá trị có kết nối đầu ra. Các khối này hoạt động như các biểu thức trong một ngôn ngữ dựa trên văn bản và tạo ra các chuỗi chứa biểu thức.
  • Khối câu lệnh là các khối không có kết nối đầu ra. Các khối này hoạt động như các câu lệnh trong ngôn ngữ dựa trên văn bản và tạo các chuỗi chứa các câu lệnh.

Cách viết trình tạo mã khối

Đối với mỗi khối tuỳ chỉnh mà bạn tạo, bạn cần viết một trình tạo mã khối cho mỗi ngôn ngữ mà bạn muốn hỗ trợ. Xin lưu ý rằng tất cả trình tạo mã khối đều được viết bằng JavaScript, ngay cả khi chúng tạo mã bằng ngôn ngữ khác.

Tất cả trình tạo mã khối đều thực hiện các bước sau:

  1. Nhập trình tạo mã ngôn ngữ.
  2. Lấy giá trị của từng trường và chuyển đổi giá trị đó thành một chuỗi mã.
  3. Nhận các chuỗi mã do các khối bên trong tạo ra, đó là các khối được đính kèm vào giá trị và dữ liệu đầu vào của câu lệnh.
  4. Tạo và trả về chuỗi mã của khối.

Khối mẫu

Ví dụ: chúng ta sẽ viết trình tạo mã JavaScript cho các khối sau.

  • custom_compare là một khối giá trị có dữ liệu đầu vào giá trị tên là LEFT, một trường thả xuống tên là OPERATOR và một trường số tên là RIGHT. Hàm này tạo một chuỗi biểu thức ở dạng '0 = 0'.

    Khối giá trị tuỳ chỉnh để so sánh.

  • custom_if là một khối câu lệnh có trường hộp đánh dấu tên là NOT, một giá trị đầu vào tên là CONDITION và một câu lệnh đầu vào tên là THEN. Hàm này tạo một chuỗi câu lệnh ở dạng 'if (...) {\n...\n};\n'.

    Khối câu lệnh tuỳ chỉnh cho câu lệnh if. Người dùng có thể sử dụng hộp đánh dấu để phủ định điều kiện if.

Tài liệu này xây dựng trình tạo theo từng bước. Bạn có thể tìm thấy các trình tạo đã hoàn tất ở phần cuối tài liệu này.

Xin lưu ý rằng các khối này chỉ nhằm minh hoạ việc tạo mã. Trong một ứng dụng thực tế, hãy sử dụng các khối logic_comparecontrols_if tích hợp sẵn.

Nhập trình tạo mã ngôn ngữ

Bạn có thể nhập trình tạo mã ngôn ngữ bằng một trong các phương thức sau. Sử dụng trình tạo đã nhập để lưu trữ trình tạo mã khối trong đối tượng forBlock.

Mô-đun

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) { /* ... */ };

Unpkg

Bạn phải thêm trình tạo sau khi thêm 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) { /* ... */ };

Tập lệnh cục bộ

Bạn phải thêm trình tạo sau khi thêm 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) { /* ... */ };

Lấy giá trị trường

Các trường cho phép người dùng nhập các giá trị như chuỗi, số và màu sắc. Để lấy giá trị của một trường, hãy gọi getFieldValue. Kết quả trả về sẽ khác nhau tuỳ theo trường. Ví dụ: các trường văn bản trả về văn bản chính xác do người dùng nhập, nhưng các trường thả xuống trả về một chuỗi trung lập về ngôn ngữ liên kết với mục mà người dùng đã chọn. Để biết thêm thông tin, hãy xem tài liệu về các trường tích hợp sẵn.

Tuỳ thuộc vào trường, bạn có thể cần chuyển đổi giá trị được trả về trước khi sử dụng giá trị đó trong mã.

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' ? '!' : '';
  ...
}

Để biết thêm thông tin, hãy xem phần Biến đổi giá trị trường.

Lấy mã từ các khối bên trong

Khối bên trong là các khối được đính kèm vào giá trị của khối và dữ liệu đầu vào của câu lệnh. Ví dụ: khối custom_if có một khối bên trong giá trị cho điều kiện if và các khối bên trong câu lệnh cho mã được thực thi nếu điều kiện là đúng.

Không giống như giá trị trường, mã bạn nhận được từ các khối bên trong đã sẵn sàng và không cần chuyển đổi.

Khối giá trị bên trong

Để lấy mã từ một khối bên trong được đính kèm vào một giá trị đầu vào, hãy gọi valueToCode. Phương thức này gọi trình tạo mã của khối bên trong.

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';
  ...
}

Khi gọi valueToCode, bạn cần cho biết toán tử mạnh nhất trong mã sẽ áp dụng cho mã của khối bên trong. Điều này cho phép valueToCode xác định xem có cần gói mã của khối bên trong trong dấu ngoặc đơn hay không.

Ví dụ: việc đánh dấu vào hộp NOT trong custom_if sẽ áp dụng toán tử phủ định logic (!) cho điều kiện. Trong trường hợp này, bạn truyền thứ tự ưu tiên của toán tử not (Order.LOGICAL_NOT) đến valueToCodevalueToCode so sánh thứ tự ưu tiên này với thứ tự ưu tiên của toán tử yếu nhất trong khối bên trong. Sau đó, lớp này sẽ gói mã của khối bên trong nếu cần:

  • Nếu CONDITION là một khối biến, valueToCode sẽ không thêm dấu ngoặc đơn vì toán tử not có thể được áp dụng trực tiếp cho một biến (!myBoolean).
  • Nếu CONDITION là một khối so sánh, valueToCode sẽ thêm dấu ngoặc đơn để toán tử phủ định áp dụng cho toàn bộ phép so sánh (!(a < b)) thay vì giá trị bên trái (!a < b).

Bạn thực sự không cần biết liệu valueToCode có thêm dấu ngoặc đơn hay không. Tất cả những gì bạn cần làm là truyền mức độ ưu tiên đến valueToCode và thêm mã được trả về vào chuỗi mã. Để biết thêm thông tin, hãy xem phần tính năng ưu tiên valueToCode.

Khối câu lệnh bên trong

Để lấy mã từ một khối bên trong được đính kèm vào dữ liệu đầu vào của câu lệnh, hãy gọi statementToCode. Phương thức này gọi trình tạo mã của khối bên trong và xử lý mã thụt lề.

custom_compare

Khối custom_compare không có bất kỳ dữ liệu đầu vào nào của câu lệnh.

custom_if

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

Bạn chỉ cần gọi statementToCode cho khối bên trong được kết nối trực tiếp với đầu vào câu lệnh. statementToCode xử lý mọi khối bổ sung được đính kèm vào khối đầu tiên.

Tạo và trả về chuỗi mã

Sau khi bạn nhận được mã cho các trường và khối bên trong, hãy tạo và trả về chuỗi mã cho khối của bạn. Nội dung bạn trả về chính xác sẽ phụ thuộc vào loại khối:

  • Khối giá trị: Trả về một mảng chứa chuỗi mã và thứ tự ưu tiên của toán tử yếu nhất trong mã. valueToCode sử dụng thuộc tính này để quyết định xem mã của bạn có cần được gói trong dấu ngoặc đơn hay không khi khối của bạn được dùng làm khối bên trong. Để biết thêm thông tin, hãy xem phần Thứ tự ưu tiên trả về.

  • Khối câu lệnh: Trả về chuỗi mã.

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;
}

Nếu sử dụng mã của một khối giá trị bên trong nhiều lần trong chuỗi mã, bạn nên lưu mã vào bộ nhớ đệm từ khối đó để tránh các lỗi nhỏ và hiệu ứng phụ không mong muốn.

Trình tạo mã hoàn chỉnh

Để tham khảo, sau đây là các trình tạo mã hoàn chỉnh cho từng khối:

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;
}