/** * @license * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** * @fileoverview Generating Lua for loop blocks. */ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Lua.loops'); import * as stringUtils from '../../core/utils/string.js'; import {NameType} from '../../core/names.js'; import {luaGenerator, Order} from '../lua.js'; /** * This is the text used to implement a
continue
. * It is also used to recognise
continue
s in generated code so that * the appropriate label can be put at the end of the loop body. * @const {string} */ const CONTINUE_STATEMENT = 'goto continue\n'; /** * If the loop body contains a "goto continue" statement, add a continue label * to the loop body. Slightly inefficient, as continue labels will be generated * in all outer loops, but this is safer than duplicating the logic of * blockToCode. * * @param {string} branch Generated code of the loop body * @param {string} indent Whitespace by which to indent a continue statement. * @return {string} Generated label or '' if unnecessary */ function addContinueLabel(branch, indent) { if (branch.indexOf(CONTINUE_STATEMENT) !== -1) { // False positives are possible (e.g. a string literal), but are harmless. return branch + indent + '::continue::\n'; } else { return branch; } }; luaGenerator.forBlock['controls_repeat_ext'] = function(block, generator) { // Repeat n times. let repeats; if (block.getField('TIMES')) { // Internal number. repeats = String(Number(block.getFieldValue('TIMES'))); } else { // External number. repeats = generator.valueToCode(block, 'TIMES', Order.NONE) || '0'; } if (stringUtils.isNumber(repeats)) { repeats = parseInt(repeats, 10); } else { repeats = 'math.floor(' + repeats + ')'; } let branch = generator.statementToCode(block, 'DO'); branch = generator.addLoopTrap(branch, block); branch = addContinueLabel(branch, generator.INDENT); const loopVar = generator.nameDB_.getDistinctName('count', NameType.VARIABLE); const code = 'for ' + loopVar + ' = 1, ' + repeats + ' do\n' + branch + 'end\n'; return code; }; luaGenerator.forBlock['controls_repeat'] = luaGenerator.forBlock['controls_repeat_ext']; luaGenerator.forBlock['controls_whileUntil'] = function(block, generator) { // Do while/until loop. const until = block.getFieldValue('MODE') === 'UNTIL'; let argument0 = generator.valueToCode( block, 'BOOL', until ? Order.UNARY : Order.NONE) || 'false'; let branch = generator.statementToCode(block, 'DO'); branch = generator.addLoopTrap(branch, block); branch = addContinueLabel(branch, generator.INDENT); if (until) { argument0 = 'not ' + argument0; } return 'while ' + argument0 + ' do\n' + branch + 'end\n'; }; luaGenerator.forBlock['controls_for'] = function(block, generator) { // For loop. const variable0 = generator.nameDB_.getName( block.getFieldValue('VAR'), NameType.VARIABLE); const startVar = generator.valueToCode(block, 'FROM', Order.NONE) || '0'; const endVar = generator.valueToCode(block, 'TO', Order.NONE) || '0'; const increment = generator.valueToCode(block, 'BY', Order.NONE) || '1'; let branch = generator.statementToCode(block, 'DO'); branch = generator.addLoopTrap(branch, block); branch = addContinueLabel(branch, generator.INDENT); let code = ''; let incValue; if (stringUtils.isNumber(startVar) && stringUtils.isNumber(endVar) && stringUtils.isNumber(increment)) { // All arguments are simple numbers. const up = Number(startVar) <= Number(endVar); const step = Math.abs(Number(increment)); incValue = (up ? '' : '-') + step; } else { code = ''; // Determine loop direction at start, in case one of the bounds // changes during loop execution. incValue = generator.nameDB_.getDistinctName( variable0 + '_inc', NameType.VARIABLE); code += incValue + ' = '; if (stringUtils.isNumber(increment)) { code += Math.abs(increment) + '\n'; } else { code += 'math.abs(' + increment + ')\n'; } code += 'if (' + startVar + ') > (' + endVar + ') then\n'; code += generator.INDENT + incValue + ' = -' + incValue + '\n'; code += 'end\n'; } code += 'for ' + variable0 + ' = ' + startVar + ', ' + endVar + ', ' + incValue; code += ' do\n' + branch + 'end\n'; return code; }; luaGenerator.forBlock['controls_forEach'] = function(block, generator) { // For each loop. const variable0 = generator.nameDB_.getName( block.getFieldValue('VAR'), NameType.VARIABLE); const argument0 = generator.valueToCode(block, 'LIST', Order.NONE) || '{}'; let branch = generator.statementToCode(block, 'DO'); branch = generator.addLoopTrap(branch, block); branch = addContinueLabel(branch, generator.INDENT); const code = 'for _, ' + variable0 + ' in ipairs(' + argument0 + ') do \n' + branch + 'end\n'; return code; }; luaGenerator.forBlock['controls_flow_statements'] = function(block, generator) { // Flow statements: continue, break. let xfix = ''; if (generator.STATEMENT_PREFIX) { // Automatic prefix insertion is switched off for this block. Add manually. xfix += generator.injectId(generator.STATEMENT_PREFIX, block); } if (generator.STATEMENT_SUFFIX) { // Inject any statement suffix here since the regular one at the end // will not get executed if the break/continue is triggered. xfix += generator.injectId(generator.STATEMENT_SUFFIX, block); } if (generator.STATEMENT_PREFIX) { const loop = block.getSurroundLoop(); if (loop && !loop.suppressPrefixSuffix) { // Inject loop's statement prefix here since the regular one at the end // of the loop will not get executed if 'continue' is triggered. // In the case of 'break', a prefix is needed due to the loop's suffix. xfix += generator.injectId(generator.STATEMENT_PREFIX, loop); } } switch (block.getFieldValue('FLOW')) { case 'BREAK': return xfix + 'break\n'; case 'CONTINUE': return xfix + CONTINUE_STATEMENT; } throw Error('Unknown flow statement.'); };