| /*=============================================================================# |
| # Copyright (c) 2007, 2020 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.r.core.rsource.ast; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.Position; |
| |
| import org.eclipse.statet.jcommons.collections.IntArrayList; |
| import org.eclipse.statet.jcommons.collections.IntList; |
| import org.eclipse.statet.jcommons.lang.NonNull; |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| import org.eclipse.statet.jcommons.text.core.BasicTextRegion; |
| import org.eclipse.statet.jcommons.text.core.TextRegion; |
| |
| import org.eclipse.statet.ltk.ast.core.Ast; |
| import org.eclipse.statet.ltk.ast.core.AstNode; |
| import org.eclipse.statet.ltk.ast.core.AstVisitor; |
| import org.eclipse.statet.r.core.model.ArgsDefinition; |
| import org.eclipse.statet.r.core.model.ArgsDefinition.Arg; |
| import org.eclipse.statet.r.core.model.RCoreFunctions; |
| import org.eclipse.statet.r.core.rlang.RTerminal; |
| import org.eclipse.statet.r.core.rsource.IRSourceConstants; |
| |
| |
| /** |
| * |
| */ |
| public class RAst extends Ast { |
| |
| |
| private static class LowestFDefAssignmentSearchVisitor extends GenericVisitor implements AstVisitor { |
| |
| private final int startOffset; |
| private final int stopOffset; |
| private boolean inAssignment; |
| private RAstNode assignment; |
| |
| |
| public LowestFDefAssignmentSearchVisitor(final int offset) { |
| this.startOffset= offset; |
| this.stopOffset= offset; |
| } |
| |
| |
| @Override |
| public void visit(final AstNode node) throws InvocationTargetException { |
| if (node instanceof RAstNode) { |
| ((RAstNode) node).acceptInR(this); |
| return; |
| } |
| if (node.getEndOffset() >= this.startOffset && this.stopOffset >= node.getStartOffset()) { |
| node.acceptInChildren(this); |
| return; |
| } |
| } |
| |
| @Override |
| public void visitNode(final RAstNode node) throws InvocationTargetException { |
| if (node.getEndOffset() >= this.startOffset && this.stopOffset >= node.getStartOffset()) { |
| node.acceptInRChildren(this); |
| return; |
| } |
| } |
| |
| @Override |
| public void visit(final Assignment node) throws InvocationTargetException { |
| if (this.inAssignment) { |
| node.getSourceChild().acceptInR(this); |
| return; |
| } |
| if (node.getEndOffset() >= this.startOffset && this.stopOffset >= node.getStartOffset()) { |
| this.inAssignment= true; |
| node.getSourceChild().acceptInR(this); |
| this.inAssignment= false; |
| return; |
| } |
| } |
| |
| @Override |
| public void visit(final FDef node) throws InvocationTargetException { |
| if (this.inAssignment |
| || (node.getEndOffset() >= this.startOffset && this.stopOffset >= node.getStartOffset()) ) { |
| RAstNode take= node; |
| RAstNode candidate= node.getRParent(); |
| // TODO: use analyzed ElementAccess if possible |
| AssignExpr assign= null; |
| while ((assign= checkAssign(candidate)) != null && assign.valueNode == take) { |
| take= assign.assignNode; |
| candidate= take.getRParent(); |
| } |
| this.assignment= take; |
| throw new OperationCanceledException(); |
| } |
| } |
| |
| } |
| |
| |
| public static RAstNode findLowestFDefAssignment(final AstNode root, final int offset) { |
| final LowestFDefAssignmentSearchVisitor visitor= new LowestFDefAssignmentSearchVisitor(offset); |
| try { |
| root.accept(visitor); |
| } |
| catch (final OperationCanceledException e) { |
| } |
| catch (final InvocationTargetException e) { |
| } |
| return visitor.assignment; |
| } |
| |
| private static class DeepestCommandsSearchVisitor extends GenericVisitor implements AstVisitor { |
| |
| private final int startOffset; |
| private final int stopOffset; |
| private RAstNode container; |
| private final List<RAstNode> commands= new ArrayList<>(); |
| |
| |
| public DeepestCommandsSearchVisitor(final int startOffset, final int stopOffset) { |
| this.startOffset= startOffset; |
| this.stopOffset= stopOffset; |
| } |
| |
| |
| @Override |
| public void visit(final AstNode node) throws InvocationTargetException { |
| if (node instanceof RAstNode) { |
| ((RAstNode) node).acceptInR(this); |
| return; |
| } |
| if (node.getEndOffset() >= this.startOffset && this.stopOffset >= node.getStartOffset()) { |
| node.acceptInChildren(this); |
| return; |
| } |
| } |
| |
| @Override |
| public void visitNode(final RAstNode node) throws InvocationTargetException { |
| if (node.endOffset >= this.startOffset && ((this.startOffset == this.stopOffset) ? |
| this.stopOffset >= node.startOffset : this.stopOffset > node.startOffset) ) { |
| if (this.container != null && this.container == node.rParent) { |
| this.commands.add(node); |
| } |
| } |
| |
| if (node.startOffset <= this.startOffset && this.stopOffset <= node.endOffset) { |
| node.acceptInRChildren(this); |
| return; |
| } |
| } |
| |
| @Override |
| public void visit(final Block node) throws InvocationTargetException { |
| if (node.startOffset <= this.startOffset && this.stopOffset <= node.endOffset) { |
| this.commands.clear(); |
| if (node.startOffset == this.startOffset && this.stopOffset == node.endOffset) { |
| this.commands.add(node); |
| this.container= null; |
| return; |
| } |
| this.container= node; |
| |
| node.acceptInRChildren(this); |
| |
| if (this.commands.isEmpty() && node.endOffset > this.startOffset) { |
| this.commands.add(node); |
| } |
| return; |
| } |
| } |
| |
| @Override |
| public void visit(final SourceComponent node) throws InvocationTargetException { |
| if (node.endOffset >= this.startOffset && this.stopOffset >= node.startOffset) { |
| this.commands.clear(); |
| this.container= node; |
| |
| node.acceptInRChildren(this); |
| return; |
| } |
| } |
| |
| } |
| |
| private static class NextCommandsSearchVisitor extends GenericVisitor implements AstVisitor { |
| |
| private final int offset; |
| private RAstNode container; |
| private RAstNode next; |
| |
| |
| public NextCommandsSearchVisitor(final int offset) { |
| this.offset= offset; |
| } |
| |
| |
| @Override |
| public void visit(final AstNode node) throws InvocationTargetException { |
| if (node instanceof RAstNode) { |
| ((RAstNode) node).acceptInR(this); |
| return; |
| } |
| if (node.getEndOffset() >= this.offset && this.offset >= node.getStartOffset()) { |
| node.acceptInChildren(this); |
| return; |
| } |
| } |
| |
| @Override |
| public void visitNode(final RAstNode node) throws InvocationTargetException { |
| if (this.next == null) { |
| if (node.startOffset >= this.offset) { |
| if (this.container != null && this.container == node.rParent) { |
| this.next= node; |
| return; |
| } |
| else { |
| node.acceptInRChildren(this); |
| return; |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void visit(final Block node) throws InvocationTargetException { |
| if (this.next == null) { |
| if (node.startOffset >= this.offset) { |
| if (this.container != null && this.container == node.rParent) { |
| this.next= node; |
| return; |
| } |
| else { |
| node.acceptInRChildren(this); |
| return; |
| } |
| } |
| if (node.endOffset >= this.offset) { |
| this.container= node; |
| node.acceptInRChildren(this); |
| return; |
| } |
| } |
| } |
| |
| @Override |
| public void visit(final SourceComponent node) throws InvocationTargetException { |
| if (this.next == null) { |
| final AstNode parent= node.getParent(); |
| if (node.endOffset >= this.offset && |
| // R script file or inside R chunk |
| (parent == null || (parent.getStartOffset() <= this.offset && this.offset <= parent.getEndOffset())) ) { |
| this.container= node; |
| node.acceptInRChildren(this); |
| return; |
| } |
| } |
| } |
| |
| } |
| |
| public static RAstNode[] findDeepestCommands(final AstNode root, final int startOffset, final int endOffset) { |
| final DeepestCommandsSearchVisitor visitor= new DeepestCommandsSearchVisitor(startOffset, endOffset); |
| try { |
| root.accept(visitor); |
| } |
| catch (final InvocationTargetException e) { |
| } |
| return visitor.commands.toArray(new RAstNode[visitor.commands.size()]); |
| } |
| |
| public static RAstNode findNextCommands(final AstNode root, final int offset) { |
| final NextCommandsSearchVisitor visitor= new NextCommandsSearchVisitor(offset); |
| try { |
| root.accept(visitor); |
| } |
| catch (final InvocationTargetException e) { |
| } |
| return visitor.next; |
| } |
| |
| public static class AssignExpr { |
| |
| public static final Object GLOBAL= new Object(); |
| public static final Object LOCAL= new Object(); |
| |
| public final Object environment; |
| public final RAstNode assignNode; |
| public final RAstNode targetNode; |
| public final RAstNode valueNode; |
| |
| public AssignExpr(final RAstNode assign, final Object env, final RAstNode target, final RAstNode source) { |
| this.assignNode= assign; |
| this.environment= env; |
| this.targetNode= target; |
| this.valueNode= source; |
| } |
| |
| } |
| |
| public static AssignExpr checkAssign(final RAstNode node) { |
| switch (node.getNodeType()) { |
| case A_LEFT: |
| case A_RIGHT: |
| case A_EQUALS: |
| final Assignment assignNode= (Assignment) node; |
| if (assignNode.isSearchOperator()) { |
| return new AssignExpr(node, AssignExpr.GLOBAL, assignNode.getTargetChild(), assignNode.getSourceChild()); |
| } |
| else { |
| return new AssignExpr(node, AssignExpr.LOCAL, assignNode.getTargetChild(), assignNode.getSourceChild()); |
| } |
| case F_CALL: |
| final FCall callNode= (FCall) node; |
| final RAstNode refChild= callNode.getRefChild(); |
| if (refChild.getNodeType() == NodeType.SYMBOL) { |
| final Symbol symbol= (Symbol) refChild; |
| if (symbol.text != null) { |
| if (symbol.text.equals(RCoreFunctions.BASE_ASSIGN_NAME)) { |
| final FCallArgMatch args= matchArgs(callNode.getArgsChild(), RCoreFunctions.DEFAULT.BASE_ASSIGN_args); |
| return new AssignExpr(node, AssignExpr.LOCAL, args.allocatedArgs[0], args.allocatedArgs[1]); |
| } |
| } |
| } |
| break; |
| case F_CALL_ARGS: |
| return checkAssign(node.getRParent()); |
| case F_CALL_ARG: |
| return checkAssign(node.getRParent().getRParent()); |
| } |
| return null; |
| } |
| |
| @NonNullByDefault |
| public static final class FCallArgMatch { |
| |
| public final ArgsDefinition argsDef; |
| public final FCall.Args argsNode; |
| public final FCall. @Nullable Arg[] allocatedArgs; |
| public final FCall.Arg[] ellipsisArgs; |
| public final FCall.Arg[] otherArgs; |
| public final int[] argsNode2argsDef; |
| |
| |
| private FCallArgMatch( |
| final ArgsDefinition argsDef, |
| final FCall.Args argsNode, |
| final FCall. @Nullable Arg[] allocatedArgs, |
| final FCall.Arg[] ellipsisArgs, |
| final FCall.Arg[] otherArgs, |
| final int[] argsNode2argsDef) { |
| this.argsDef= argsDef; |
| this.argsNode= argsNode; |
| this.allocatedArgs= allocatedArgs; |
| this.ellipsisArgs= ellipsisArgs; |
| this.otherArgs= otherArgs; |
| this.argsNode2argsDef= argsNode2argsDef; |
| } |
| |
| |
| public FCall. @Nullable Arg getArgNode(final String name) { |
| final int idx= this.argsDef.indexOf(name); |
| if (idx >= 0) { |
| return this.allocatedArgs[idx]; |
| } |
| else { |
| return null; |
| } |
| } |
| |
| public FCall. @Nullable Arg getArgNode(final int callArgIdx) { |
| if (callArgIdx >= 0) { |
| return this.allocatedArgs[callArgIdx]; |
| } |
| else { |
| return null; |
| } |
| } |
| |
| public @Nullable RAstNode getArgValueNode(final String name) { |
| final int idx= this.argsDef.indexOf(name); |
| if (idx >= 0 && this.allocatedArgs[idx] != null) { |
| return this.allocatedArgs[idx].getValueChild(); |
| } |
| else { |
| return null; |
| } |
| } |
| |
| public @Nullable RAstNode getArgValueNode(final int callArgIdx) { |
| if (callArgIdx >= 0 && this.allocatedArgs[callArgIdx] != null) { |
| return this.allocatedArgs[callArgIdx].getValueChild(); |
| } |
| else { |
| return null; |
| } |
| } |
| |
| public ArgsDefinition. @Nullable Arg getArgDef(final int callArgIdx) { |
| if (callArgIdx >= 0 && this.argsDef.size() > 0) { |
| if (callArgIdx < this.argsNode2argsDef.length) { |
| if (this.argsNode2argsDef[callArgIdx] >= 0) { |
| return this.argsDef.get(this.argsNode2argsDef[callArgIdx]); |
| } |
| } |
| else if (callArgIdx == 0 && this.argsNode2argsDef.length == 0){ |
| return this.argsDef.get(0); |
| } |
| } |
| return null; |
| } |
| |
| |
| @Override |
| public String toString() { |
| return Arrays.toString(this.argsNode2argsDef); |
| } |
| |
| } |
| |
| public static final FCall.Arg @NonNull [] NO_ARGS= new FCall.Arg[0]; |
| |
| private static final int FAIL= -1; |
| private static final int ELLIPSIS= -2; |
| private static final int TEST_PARTIAL= -3; |
| private static final int TEST_POSITION= -4; |
| |
| /** |
| * Reads the arguments of a function call for the given function definition |
| * |
| * If follows mainly the R rules expect if R would fail. |
| * |
| * 1) Exact matching on tags. |
| * a) First supplied named argument matching exactly a formal name |
| * is assigned to its formal arguments (position) in <code>argsNode</code> |
| * b) Additional supplied arguments matching exactly the formal name |
| * with an assignment of a) (error) are added to <code>otherArgs</code> |
| * 2) Partial matching on tags |
| * a) If the name of the supplied argument matches partially more than one |
| * unmatched formal name (error), it is added to <code>otherArgs</code> |
| * b) First supplied argument matching partially one unmatched formal name |
| * is assigned to its formal arguments (position) in <code>argsNode</code> |
| * c) Additional supplied arguments matching partially the unmatched formal name |
| * with an assignment of b) (error) are treated like arguments without any match |
| * in 1) and 2) continue with 3). |
| * 3) Positional matching. |
| * a) Any unnamed supplied argument is assigned to unmatched formal |
| * arguments (empty positions) in <code>argsNode</code>, in order. |
| * Until all If there is a ‘...’ argument or there is no more empty position. |
| * b) If there is a ‘...’ argument, all remaining supplied arguments |
| * are added to <code>ellipsisArgs</code> |
| * 4) Remaining supplied arguments (error) are assigned to <code>otherArgs</code> |
| * |
| * (see paragraph 'Argument matching' in 'R Language Definition') |
| * |
| * @param argsNode the arguments of the call |
| * @param argsDef the arguments definition |
| * @return |
| */ |
| @NonNullByDefault |
| public static FCallArgMatch matchArgs(final FCall.Args argsNode, final ArgsDefinition argsDef) { |
| final int nodeArgsCount= argsNode.getChildCount(); |
| final int defArgsCount= argsDef.size(); |
| final FCall. @Nullable Arg[] allocatedArgs= (defArgsCount > 0) ? |
| new FCall. @Nullable Arg[defArgsCount] : NO_ARGS; |
| final int ellipsisDefIdx= argsDef.indexOf("..."); //$NON-NLS-1$ |
| |
| final int[] match= new int[nodeArgsCount]; |
| int ellipsisCount= 0; |
| int failCount= 0; |
| boolean testPartial= false; |
| boolean testPosition= false; |
| |
| for (int nodeIdx= 0; nodeIdx < nodeArgsCount; nodeIdx++) { |
| final FCall.Arg argNode= argsNode.getChild(nodeIdx); |
| if (argNode.hasName()) { |
| final Arg arg= argsDef.get(argNode.getNameChild().getText()); |
| if (arg != null && arg.index != ellipsisDefIdx) { |
| if (allocatedArgs[arg.index] == null) { |
| allocatedArgs[arg.index]= argNode; |
| match[nodeIdx]= arg.index; |
| } |
| else { |
| failCount++; |
| match[nodeIdx]= FAIL; |
| } |
| } |
| else { |
| testPartial= true; |
| match[nodeIdx]= TEST_PARTIAL; |
| } |
| } |
| else { |
| testPosition= true; |
| match[nodeIdx]= TEST_POSITION; |
| } |
| } |
| |
| final int ellipsisType= (ellipsisDefIdx >= 0) ? ELLIPSIS : FAIL; |
| final int testStop= (ellipsisDefIdx >= 0) ? ellipsisDefIdx : defArgsCount; |
| if (testPartial) { |
| FCall. @Nullable Arg[] partialArgs= null; |
| ITER_ARGS: for (int nodeIdx= 0; nodeIdx < nodeArgsCount; nodeIdx++) { |
| if (match[nodeIdx] == TEST_PARTIAL) { |
| final FCall.Arg argNode= argsNode.getChild(nodeIdx); |
| final String name= argNode.getNameChild().getText(); |
| int matchIdx= -1; |
| for (int defIdx= 0; defIdx < testStop; defIdx++) { |
| if (allocatedArgs[defIdx] == null && argsDef.get(defIdx).name.startsWith(name)) { |
| if (matchIdx < 0) { |
| matchIdx= defIdx; |
| } |
| else { |
| failCount++; |
| match[nodeIdx]= FAIL; |
| continue ITER_ARGS; |
| } |
| } |
| } |
| if (matchIdx >= 0) { |
| if (partialArgs == null) { |
| partialArgs= new FCall. @Nullable Arg[testStop]; |
| partialArgs[matchIdx]= argNode; |
| match[nodeIdx]= matchIdx; |
| continue ITER_ARGS; |
| } |
| if (partialArgs[matchIdx] == null) { |
| partialArgs[matchIdx]= argNode; |
| match[nodeIdx]= matchIdx; |
| continue ITER_ARGS; |
| } |
| } |
| ellipsisCount++; |
| match[nodeIdx]= ellipsisType; |
| continue ITER_ARGS; |
| } |
| } |
| if (partialArgs != null) { |
| for (int i= 0; i < testStop; i++) { |
| if (partialArgs[i] != null) { |
| allocatedArgs[i]= partialArgs[i]; |
| } |
| } |
| } |
| } |
| |
| if (testPosition) { |
| int defIdx= 0; |
| ITER_ARGS: for (int nodeIdx= 0; nodeIdx < nodeArgsCount; nodeIdx++) { |
| if (match[nodeIdx] == TEST_POSITION) { |
| ITER_DEFS: while (defIdx < testStop) { |
| if (allocatedArgs[defIdx] == null) { |
| match[nodeIdx]= defIdx; |
| allocatedArgs[defIdx++]= argsNode.getChild(nodeIdx); |
| continue ITER_ARGS; |
| } |
| else { |
| defIdx++; |
| continue ITER_DEFS; |
| } |
| } |
| match[nodeIdx]= ellipsisType; |
| ellipsisCount++; |
| continue ITER_ARGS; |
| } |
| } |
| } |
| |
| if (ellipsisType != ELLIPSIS) { |
| failCount+= ellipsisCount; |
| ellipsisCount= 0; |
| } |
| final FCall.Arg[] ellipsisArgs= (ellipsisCount > 0) ? new FCall. @NonNull Arg[ellipsisCount] : NO_ARGS; |
| final FCall.Arg[] otherArgs= (failCount > 0) ? new FCall. @NonNull Arg[failCount] : NO_ARGS; |
| if (ellipsisCount > 0 || failCount > 0) { |
| int ellipsisIdx= 0; |
| int otherIdx= 0; |
| ITER_ARGS: for (int nodeIdx= 0; nodeIdx < nodeArgsCount; nodeIdx++) { |
| switch (match[nodeIdx]) { |
| case ELLIPSIS: |
| ellipsisArgs[ellipsisIdx++]= argsNode.getChild(nodeIdx); |
| match[nodeIdx]= ellipsisDefIdx; |
| continue ITER_ARGS; |
| case FAIL: |
| otherArgs[otherIdx++]= argsNode.getChild(nodeIdx); |
| continue ITER_ARGS; |
| } |
| } |
| } |
| return new FCallArgMatch(argsDef, argsNode, |
| allocatedArgs, ellipsisArgs, otherArgs, match); |
| } |
| |
| /** |
| * @return position of the element name, if possible (symbol or strings), otherwise null |
| */ |
| public static Position getElementNamePosition(final RAstNode node) { |
| switch (node.getNodeType()) { |
| case SYMBOL: |
| if (node.getOperator(0) == RTerminal.SYMBOL_G) { |
| if ((node.getStatusCode() & IRSourceConstants.STATUS_MASK_12) == IRSourceConstants.STATUS12_SYNTAX_TOKEN_NOT_CLOSED) { |
| return new Position(node.getStartOffset() + 1, node.getLength() - 1); |
| } |
| return new Position(node.getStartOffset() + 1, node.getLength() - 2); |
| } |
| return new Position(node.getStartOffset(), node.getLength()); |
| case STRING_CONST: |
| if ((node.getStatusCode() & IRSourceConstants.STATUS_MASK_12) == IRSourceConstants.STATUS12_SYNTAX_TOKEN_NOT_CLOSED) { |
| return new Position(node.getStartOffset() + 1, node.getLength() - 1); |
| } |
| return new Position(node.getStartOffset() + 1, node.getLength() - 2); |
| default: |
| return null; |
| } |
| } |
| |
| /** |
| * @return position of the element name, if possible (symbol or strings), otherwise the node range |
| */ |
| public static TextRegion getElementNameRegion(final RAstNode node) { |
| switch (node.getNodeType()) { |
| case SYMBOL: |
| if (node.getOperator(0) == RTerminal.SYMBOL_G) { |
| if ((node.getStatusCode() & IRSourceConstants.STATUS_MASK_12) == IRSourceConstants.STATUS12_SYNTAX_TOKEN_NOT_CLOSED) { |
| return new BasicTextRegion(node.getStartOffset() + 1, node.getEndOffset()); |
| } |
| return new BasicTextRegion(node.getStartOffset() + 1, node.getEndOffset() - 1); |
| } |
| return node; |
| case STRING_CONST: |
| if ((node.getStatusCode() & IRSourceConstants.STATUS_MASK_12) == IRSourceConstants.STATUS12_SYNTAX_TOKEN_NOT_CLOSED) { |
| return new BasicTextRegion(node.getStartOffset() + 1, node.getEndOffset()); |
| } |
| return new BasicTextRegion(node.getStartOffset() + 1, node.getEndOffset() - 1); |
| default: |
| return node; |
| } |
| } |
| |
| public static boolean hasErrors(final RAstNode node) { |
| return ((node.getStatusCode() & |
| (IRSourceConstants.STATUSFLAG_REAL_ERROR | IRSourceConstants.STATUSFLAG_ERROR_IN_CHILD) |
| ) != 0 ); |
| } |
| |
| public static int[] computeRExpressionIndex(RAstNode node, final RAstNode baseNode) { |
| final IntList topdown= new IntArrayList(); |
| while (node != baseNode) { |
| final RAstNode parent= node.getRParent(); |
| switch (parent.getNodeType()) { |
| |
| // list |
| case SOURCELINES: |
| case F_DEF_ARGS: |
| // [[1]]= name |
| case F_CALL: |
| topdown.add(parent.getChildIndex(node) + 1); |
| node= parent; |
| continue; |
| |
| // [[1]]= operator |
| case BLOCK: |
| case GROUP: |
| case SUB_INDEXED_S: |
| case SUB_INDEXED_D: |
| case NS_GET: |
| case NS_GET_INT: |
| case SUB_NAMED_PART: |
| case SUB_NAMED_SLOT: |
| case POWER: |
| case SIGN: |
| case SEQ: |
| case SPECIAL: |
| case MULT: |
| case ADD: |
| case RELATIONAL: |
| case NOT: |
| case AND: |
| case OR: |
| case MODEL: |
| case A_LEFT: |
| case A_RIGHT: |
| case A_EQUALS: |
| case A_COLON: |
| case HELP: |
| case C_IF: |
| case C_FOR: |
| case C_WHILE: |
| case C_REPEAT: |
| case F_DEF: |
| topdown.add(parent.getChildIndex(node) + 2); |
| node= parent; |
| continue; |
| |
| // part of parent element |
| case SUB_INDEXED_ARGS: |
| if (parent == baseNode) { |
| continue; |
| } |
| topdown.add(parent.getChildIndex(node) + 3); |
| node= parent.getRParent(); |
| continue; |
| case C_IN: |
| case F_CALL_ARGS: |
| if (parent == baseNode) { |
| continue; |
| } |
| topdown.add(parent.getChildIndex(node) + 2); |
| node= parent.getRParent(); |
| continue; |
| |
| case SUB_INDEXED_ARG: // -> ARGS |
| case F_DEF_ARG: // -> ARGS |
| case F_CALL_ARG: // -> ARGS |
| node= parent; |
| continue; |
| |
| case ERROR: |
| case ERROR_TERM: |
| case DUMMY: |
| return null; |
| |
| default: |
| throw new IllegalStateException("Unexpected parent: type= " + node.getNodeType()); //$NON-NLS-1$ |
| } |
| } |
| final int l= topdown.size(); |
| final int[] path= new int[l]; |
| for (int i= 0; i < l;) { |
| path[i]= topdown.getAt(l - ++i); |
| } |
| return path; |
| } |
| |
| public static List<RAstNode> computeRExpressionNodes(RAstNode node, final RAstNode baseNode) { |
| final List<RAstNode> nodes= new ArrayList<>(); |
| while (node != baseNode) { |
| switch (node.getNodeType()) { |
| |
| // list |
| case SOURCELINES: |
| // [[1]]= name |
| case STRING_CONST: |
| case NUM_CONST: |
| case NULL_CONST: |
| case SYMBOL: |
| case BLOCK: |
| case GROUP: |
| case SUB_INDEXED_S: |
| case SUB_INDEXED_D: |
| case NS_GET: |
| case NS_GET_INT: |
| case SUB_NAMED_PART: |
| case SUB_NAMED_SLOT: |
| case POWER: |
| case SIGN: |
| case SEQ: |
| case SPECIAL: |
| case MULT: |
| case ADD: |
| case RELATIONAL: |
| case NOT: |
| case AND: |
| case OR: |
| case MODEL: |
| case A_LEFT: |
| case A_RIGHT: |
| case A_EQUALS: |
| case A_COLON: |
| case HELP: |
| case C_IF: |
| case C_FOR: |
| case C_WHILE: |
| case C_REPEAT: |
| case F_DEF: |
| case F_DEF_ARGS: |
| case F_CALL: |
| nodes.add(node); |
| node= node.getRParent(); |
| continue; |
| |
| // part of parent element |
| case SUB_INDEXED_ARGS: |
| case C_IN: |
| case F_CALL_ARGS: |
| |
| case SUB_INDEXED_ARG: // -> ARGS |
| case F_DEF_ARG: // -> ARGS |
| case F_CALL_ARG: // -> ARGS |
| node= node.getRParent(); |
| continue; |
| |
| case ERROR: |
| case ERROR_TERM: |
| case DUMMY: |
| return null; |
| |
| default: |
| throw new IllegalStateException("Unexpected parent: type= " + node.getNodeType()); //$NON-NLS-1$ |
| } |
| } |
| if (nodes.size() > 1) { |
| Collections.reverse(nodes); |
| } |
| return nodes; |
| } |
| |
| public static RAstNode getRRootNode(RAstNode node, final IRegion region) { |
| if (region == null) { |
| return node.getRRoot(); |
| } |
| while (node.getRParent() != null) { |
| final RAstNode parent= node.getRParent(); |
| final int beginDiff; |
| final int endDiff; |
| if ((beginDiff= region.getOffset() - parent.getStartOffset()) > 0 |
| || (endDiff= region.getOffset() + region.getLength() - parent.getEndOffset()) < 0 ) { |
| return node; |
| } |
| else if (beginDiff == 0 && endDiff == 0) { |
| return (parent.getNodeType() == NodeType.SOURCELINES) ? node : parent; |
| } |
| else { |
| node= parent; |
| continue; |
| } |
| } |
| return node; |
| } |
| |
| public static boolean isParentChild(final RAstNode parent, RAstNode child) { |
| while ((child= child.getRParent()) != null) { |
| if (child == parent) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| |
| public static Object toJava(RAstNode node) { |
| while (node != null) { |
| switch (node.getNodeType()) { |
| case NUM_CONST: |
| switch (node.getOperator(0)) { |
| case NUM_NUM: |
| return parseNum(node.getText()); |
| case NUM_INT: |
| return parseInt(node.getText()); |
| case TRUE: |
| return Boolean.TRUE; |
| case FALSE: |
| return Boolean.FALSE; |
| default: |
| break; |
| } |
| return null; |
| case F_CALL_ARG: |
| node= ((FCall.Arg) node).getValueChild(); |
| continue; |
| default: |
| return null; |
| } |
| } |
| return null; |
| } |
| |
| public static Integer toJavaInt(RAstNode node) { |
| while (node != null) { |
| switch (node.getNodeType()) { |
| case NUM_CONST: |
| switch (node.getOperator(0)) { |
| case NUM_NUM: { |
| final Double num= parseNum(node.getText()); |
| if (num != null && num.doubleValue() == Math.rint(num.doubleValue())) { |
| return num.intValue(); |
| } |
| break; } |
| case NUM_INT: |
| return parseInt(node.getText()); |
| case TRUE: |
| return 1; |
| case FALSE: |
| return 0; |
| default: |
| break; |
| } |
| return null; |
| case F_CALL_ARG: |
| node= ((FCall.Arg) node).getValueChild(); |
| continue; |
| default: |
| return null; |
| } |
| } |
| return null; |
| } |
| |
| public static Float toJavaFloat(RAstNode node) { |
| while (node != null) { |
| switch (node.getNodeType()) { |
| case NUM_CONST: |
| switch (node.getOperator(0)) { |
| case NUM_NUM: { |
| final Double num= parseNum(node.getText()); |
| if (num != null && Math.abs(num.doubleValue()) <= Float.MAX_VALUE) { |
| return num.floatValue(); |
| } |
| break; } |
| case NUM_INT: { |
| final Integer num= parseInt(node.getText()); |
| if (num != null) { |
| return num.floatValue(); |
| } |
| break; } |
| case TRUE: |
| return 1f; |
| case FALSE: |
| return 0f; |
| default: |
| break; |
| } |
| return null; |
| case F_CALL_ARG: |
| node= ((FCall.Arg) node).getValueChild(); |
| continue; |
| default: |
| return null; |
| } |
| } |
| return null; |
| } |
| |
| public static Double toJavaDouble(RAstNode node) { |
| while (node != null) { |
| switch (node.getNodeType()) { |
| case NUM_CONST: |
| switch (node.getOperator(0)) { |
| case NUM_NUM: { |
| final Double num= parseNum(node.getText()); |
| if (num != null) { |
| return num.doubleValue(); |
| } |
| break; } |
| case NUM_INT: { |
| final Integer num= parseInt(node.getText()); |
| if (num != null) { |
| return num.doubleValue(); |
| } |
| break; } |
| case TRUE: |
| return 1.0; |
| case FALSE: |
| return 0.0; |
| default: |
| break; |
| } |
| return null; |
| case F_CALL_ARG: |
| node= ((FCall.Arg) node).getValueChild(); |
| continue; |
| default: |
| return null; |
| } |
| } |
| return null; |
| } |
| |
| private static Double parseNum(final String text) { |
| if (text != null && !text.isEmpty()) { |
| try { |
| return Double.valueOf(text); |
| } |
| catch (final NumberFormatException e) {} |
| } |
| return null; |
| } |
| |
| private static Integer parseInt(String text) { |
| if (text != null && !text.isEmpty()) { |
| try { |
| if (text.endsWith("L")) { //$NON-NLS-1$ |
| text= text.substring(0, text.length() - 1); |
| } |
| if (text.startsWith("0x")) { //$NON-NLS-1$ |
| text= text.substring(2); |
| return Integer.parseInt(text, 16); |
| } |
| else { |
| return Integer.parseInt(text); |
| } |
| } |
| catch (final NumberFormatException e) {} |
| } |
| return null; |
| } |
| |
| } |