/** * @license * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** * @file Generating Lua for loop blocks. */ // Former goog.module ID: Blockly.Lua.loops import * as stringUtils from '../../core/utils/string.js'; import type {Block} from '../../core/block.js'; import type {ControlFlowInLoopBlock} from '../../blocks/loops.js'; import type {LuaGenerator} from './lua_generator.js'; import {NameType} from '../../core/names.js'; import {Order} from './lua_generator.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 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 branch Generated code of the loop body * @param indent Whitespace by which to indent a continue statement. * @returns Generated label or '' if unnecessary */ function addContinueLabel(branch: string, indent: string): string { 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; } } export function controls_repeat_ext( block: Block, generator: LuaGenerator, ): string { // 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; } export const controls_repeat = controls_repeat_ext; export function controls_whileUntil( block: Block, generator: LuaGenerator, ): string { // 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'; } export function controls_for(block: Block, generator: LuaGenerator): string { // For loop. const variable0 = generator.getVariableName(block.getFieldValue('VAR')); 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 as unknown as number) + '\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; } export function controls_forEach( block: Block, generator: LuaGenerator, ): string { // For each loop. const variable0 = generator.getVariableName(block.getFieldValue('VAR')); 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; } export function controls_flow_statements( block: Block, generator: LuaGenerator, ): string { // 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 as ControlFlowInLoopBlock).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.'); }