/** * @license * Copyright 2019 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** * The class representing a basic cursor. * Used to demo switching between different cursors. * * @class */ // Former goog.module ID: Blockly.BasicCursor import * as registry from '../registry.js'; import {ASTNode} from './ast_node.js'; import {Cursor} from './cursor.js'; /** * Class for a basic cursor. * This will allow the user to get to all nodes in the AST by hitting next or * previous. */ export class BasicCursor extends Cursor { /** Name used for registering a basic cursor. */ static readonly registrationName = 'basicCursor'; constructor() { super(); } /** * Find the next node in the pre order traversal. * * @returns The next node, or null if the current node is not set or there is * no next value. */ override next(): ASTNode | null { const curNode = this.getCurNode(); if (!curNode) { return null; } const newNode = this.getNextNode_(curNode, this.validNode_); if (newNode) { this.setCurNode(newNode); } return newNode; } /** * For a basic cursor we only have the ability to go next and previous, so * in will also allow the user to get to the next node in the pre order * traversal. * * @returns The next node, or null if the current node is not set or there is * no next value. */ override in(): ASTNode | null { return this.next(); } /** * Find the previous node in the pre order traversal. * * @returns The previous node, or null if the current node is not set or there * is no previous value. */ override prev(): ASTNode | null { const curNode = this.getCurNode(); if (!curNode) { return null; } const newNode = this.getPreviousNode_(curNode, this.validNode_); if (newNode) { this.setCurNode(newNode); } return newNode; } /** * For a basic cursor we only have the ability to go next and previous, so * out will allow the user to get to the previous node in the pre order * traversal. * * @returns The previous node, or null if the current node is not set or there * is no previous value. */ override out(): ASTNode | null { return this.prev(); } /** * Uses pre order traversal to navigate the Blockly AST. This will allow * a user to easily navigate the entire Blockly AST without having to go in * and out levels on the tree. * * @param node The current position in the AST. * @param isValid A function true/false depending on whether the given node * should be traversed. * @returns The next node in the traversal. */ protected getNextNode_( node: ASTNode | null, isValid: (p1: ASTNode | null) => boolean, ): ASTNode | null { if (!node) { return null; } const newNode = node.in() || node.next(); if (isValid(newNode)) { return newNode; } else if (newNode) { return this.getNextNode_(newNode, isValid); } const siblingOrParent = this.findSiblingOrParent(node.out()); if (isValid(siblingOrParent)) { return siblingOrParent; } else if (siblingOrParent) { return this.getNextNode_(siblingOrParent, isValid); } return null; } /** * Reverses the pre order traversal in order to find the previous node. This * will allow a user to easily navigate the entire Blockly AST without having * to go in and out levels on the tree. * * @param node The current position in the AST. * @param isValid A function true/false depending on whether the given node * should be traversed. * @returns The previous node in the traversal or null if no previous node * exists. */ protected getPreviousNode_( node: ASTNode | null, isValid: (p1: ASTNode | null) => boolean, ): ASTNode | null { if (!node) { return null; } let newNode: ASTNode | null = node.prev(); if (newNode) { newNode = this.getRightMostChild(newNode); } else { newNode = node.out(); } if (isValid(newNode)) { return newNode; } else if (newNode) { return this.getPreviousNode_(newNode, isValid); } return null; } /** * Decides what nodes to traverse and which ones to skip. Currently, it * skips output, stack and workspace nodes. * * @param node The AST node to check whether it is valid. * @returns True if the node should be visited, false otherwise. */ protected validNode_(node: ASTNode | null): boolean { let isValid = false; const type = node && node.getType(); if ( type === ASTNode.types.OUTPUT || type === ASTNode.types.INPUT || type === ASTNode.types.FIELD || type === ASTNode.types.NEXT || type === ASTNode.types.PREVIOUS || type === ASTNode.types.WORKSPACE ) { isValid = true; } return isValid; } /** * From the given node find either the next valid sibling or parent. * * @param node The current position in the AST. * @returns The parent AST node or null if there are no valid parents. */ private findSiblingOrParent(node: ASTNode | null): ASTNode | null { if (!node) { return null; } const nextNode = node.next(); if (nextNode) { return nextNode; } return this.findSiblingOrParent(node.out()); } /** * Get the right most child of a node. * * @param node The node to find the right most child of. * @returns The right most child of the given node, or the node if no child * exists. */ private getRightMostChild(node: ASTNode | null): ASTNode | null { if (!node!.in()) { return node; } let newNode = node!.in(); while (newNode && newNode.next()) { newNode = newNode.next(); } return this.getRightMostChild(newNode); } } registry.register( registry.Type.CURSOR, BasicCursor.registrationName, BasicCursor, );