| /*=============================================================================# |
| # Copyright (c) 2009, 2021 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.yaml.core.source; |
| |
| import java.util.ArrayDeque; |
| import java.util.Deque; |
| |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| |
| import org.yaml.snakeyaml.tokens.Token; |
| |
| import org.eclipse.statet.ecommons.text.core.treepartitioner.TreePartitionNode; |
| import org.eclipse.statet.ecommons.text.core.treepartitioner.TreePartitionNodeScan; |
| import org.eclipse.statet.ecommons.text.core.treepartitioner.TreePartitionNodeScanner; |
| import org.eclipse.statet.ecommons.text.core.treepartitioner.TreePartitionNodeType; |
| |
| import org.eclipse.statet.internal.yaml.snakeyaml.scanner.ScannerImpl; |
| import org.eclipse.statet.internal.yaml.snakeyaml.scanner.StreamReader; |
| |
| |
| public class YamlPartitionNodeScanner implements TreePartitionNodeScanner { |
| |
| |
| public static final TreePartitionNode findYamlRootNode(TreePartitionNode node) { |
| while (true) { |
| if (node == null) { |
| return null; |
| } |
| if (node.getType() instanceof YamlPartitionNodeType) { |
| break; |
| } |
| node= node.getParent(); |
| } |
| // (node.getType() instanceof YamlPartitionNodeType) |
| TreePartitionNode parentNode; |
| while ((parentNode= node.getParent()) != null |
| && parentNode.getType() instanceof YamlPartitionNodeType) { |
| node= parentNode; |
| } |
| return node; |
| } |
| |
| |
| private static class Pos { |
| |
| private final int startOffset; |
| private final int endOffset; |
| |
| public Pos(final int startOffset, final int endOffset) { |
| this.startOffset= startOffset; |
| this.endOffset= endOffset; |
| } |
| |
| public int getStartOffset() { |
| return this.startOffset; |
| } |
| |
| public int getEndOffset() { |
| return this.endOffset; |
| } |
| |
| } |
| |
| |
| private final ScannerImpl scanner= new ScannerImpl(new StreamReader(""), false, false, false) { //$NON-NLS-1$ |
| |
| @Override |
| protected void handleComment(final int startIndex, final int endIndex) { |
| YamlPartitionNodeScanner.this.comments.addLast(new Pos(startIndex, endIndex)); |
| } |
| |
| }; |
| |
| private final Deque<Pos> comments= new ArrayDeque<>(); |
| |
| private TreePartitionNodeScan scan; |
| |
| private TreePartitionNode rootNode; |
| |
| /** The current node */ |
| private TreePartitionNode node; |
| /** The current node type */ |
| private YamlPartitionNodeType type; |
| |
| |
| public YamlPartitionNodeScanner() { |
| } |
| |
| |
| @Override |
| public int getRestartOffset(TreePartitionNode node, final IDocument document, int offset) |
| throws BadLocationException { |
| final YamlPartitionNodeType rootType= getDefaultRootType(); |
| TreePartitionNode parent= node.getParent(); |
| if (parent != null) { |
| while (parent.getType() != rootType) { |
| node= parent; |
| parent= node.getParent(); |
| } |
| |
| // start at line start, but never inside a child |
| int idx= parent.indexOfChild(node); |
| while (true) { |
| final int line= document.getLineOfOffset(node.getStartOffset()); |
| offset= document.getLineOffset(line); |
| if (idx > 0) { |
| node= parent.getChild(--idx); |
| if (offset < node.getEndOffset()) { |
| continue; |
| } |
| } |
| break; |
| } |
| } |
| return offset; |
| } |
| |
| @Override |
| public YamlPartitionNodeType getDefaultRootType() { |
| return YamlPartitionNodeType.DEFAULT_ROOT; |
| } |
| |
| @Override |
| public void execute(final TreePartitionNodeScan scan) { |
| this.scan= scan; |
| this.node= null; |
| this.comments.clear(); |
| |
| setRange(scan.getStartOffset(), scan.getEndOffset()); |
| init(); |
| assert (this.rootNode != null && this.node != null); |
| |
| process(); |
| } |
| |
| protected TreePartitionNodeScan getScan() { |
| return this.scan; |
| } |
| |
| protected void setRange(final int startOffset, final int endOffset) { |
| try { |
| final String s= getScan().getDocument().get(startOffset, endOffset - startOffset); |
| this.scanner.reset(s, startOffset); |
| } |
| catch (final BadLocationException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| protected void init() { |
| final TreePartitionNode beginNode= getScan().getBeginNode(); |
| if (beginNode.getType() instanceof YamlPartitionNodeType) { |
| initNode(beginNode, (YamlPartitionNodeType) beginNode.getType()); |
| } |
| else { |
| this.node= beginNode; |
| addNode(getDefaultRootType(), getScan().getStartOffset()); |
| this.rootNode= this.node; |
| } |
| } |
| |
| |
| protected final void initNode(final TreePartitionNode node, final YamlPartitionNodeType type) { |
| if (this.node != null) { |
| throw new IllegalStateException(); |
| } |
| this.node= node; |
| this.type= type; |
| this.rootNode= findYamlRootNode(node); |
| } |
| |
| protected final void addNode(final YamlPartitionNodeType type, final int offset) { |
| checkComment(offset); |
| this.node= this.scan.add(type, this.node, offset, 0); |
| this.type= type; |
| } |
| |
| protected final TreePartitionNode getNode() { |
| return this.node; |
| } |
| |
| protected final void exitNode(final int offset, final int flags) { |
| checkComment(offset); |
| this.scan.expand(this.node, offset, flags, true); |
| this.node= this.node.getParent(); |
| this.type= (YamlPartitionNodeType) this.node.getType(); |
| } |
| |
| protected final boolean exitNode(final YamlPartitionNodeType type, final YamlPartitionNodeType typeAlt, |
| final int offset) { |
| int n= 1; |
| TreePartitionNode aNode= this.node; |
| while (aNode != null) { |
| final TreePartitionNodeType aType= aNode.getType(); |
| if (aType == type || aType == typeAlt) { |
| while (n > 0) { |
| exitNode(offset, 0); |
| n--; |
| } |
| return true; |
| } |
| if (!(aType instanceof YamlPartitionNodeType)) { |
| return false; |
| } |
| aNode= aNode.getParent(); |
| n++; |
| } |
| return false; |
| } |
| |
| // protected final void exitNode() { |
| // this.node= this.node.getParent(); |
| // this.type= (YamlPartitionNodeType) this.node.getType(); |
| // } |
| |
| protected final void exitNodesTo(final TreePartitionNode stopNode, |
| final int offset, final int flags) { |
| while (this.node != stopNode) { |
| exitNode(offset, flags); |
| } |
| } |
| |
| |
| private void process() { |
| boolean key= false; |
| while (true) { |
| final Token token= this.scanner.nextToken(); |
| if (token == null) { |
| handleEOF(this.type); |
| return; |
| } |
| |
| switch (token.getTokenId()) { |
| case StreamStart: |
| case StreamEnd: |
| case DocumentStart: |
| case DocumentEnd: |
| exitNodesTo(this.rootNode, token.getStartMark().getIndex(), 0); |
| checkComment(token.getStartMark().getIndex()); |
| continue; |
| case Directive: |
| addNode(YamlPartitionNodeType.DIRECTIVE, token.getStartMark().getIndex()); |
| exitNode(token.getEndMark().getIndex(), 0); |
| continue; |
| case BlockMappingStart: |
| addNode(YamlPartitionNodeType.BLOCK_MAPPING, token.getStartMark().getIndex()); |
| continue; |
| case BlockSequenceStart: |
| addNode(YamlPartitionNodeType.BLOCK_SEQUENCE, token.getStartMark().getIndex()); |
| continue; |
| case BlockEntry: |
| continue; |
| case BlockEnd: |
| exitNode(YamlPartitionNodeType.BLOCK_MAPPING, YamlPartitionNodeType.BLOCK_SEQUENCE, |
| token.getEndMark().getIndex() ); |
| continue; |
| case FlowMappingStart: |
| addNode(YamlPartitionNodeType.FLOAT_MAPPING, token.getStartMark().getIndex()); |
| continue; |
| case FlowSequenceStart: |
| addNode(YamlPartitionNodeType.FLOAT_SEQUENCE, token.getStartMark().getIndex()); |
| continue; |
| case FlowEntry: |
| continue; |
| case FlowMappingEnd: |
| exitNode(YamlPartitionNodeType.FLOAT_MAPPING, null, |
| token.getEndMark().getIndex() ); |
| continue; |
| case FlowSequenceEnd: |
| exitNode(YamlPartitionNodeType.FLOAT_SEQUENCE, null, |
| token.getEndMark().getIndex() ); |
| continue; |
| case Key: |
| key= true; |
| // addNode(YamlPartitionNodeType.KEY, token.getStartMark().getIndex()); |
| // if (checkToken(Token.ID.Scalar)) { |
| // token= nextToken(); |
| // } |
| // exitNode(token.getEndMark().getIndex()); |
| continue; |
| case Tag: |
| addNode(YamlPartitionNodeType.TAG, token.getStartMark().getIndex()); |
| exitNode(token.getEndMark().getIndex(), 0); |
| continue; |
| case Anchor: |
| case Alias: |
| continue; |
| case Value: |
| key= false; |
| // addNode(YamlPartitionNodeType.VALUE, token.getStartMark().getIndex()); |
| // if (checkToken(Token.ID.Scalar)) { |
| // token= nextToken(); |
| // } |
| // exitNode(token.getEndMark().getIndex()); |
| continue; |
| case Scalar: |
| addNode((key) ? YamlPartitionNodeType.KEY : YamlPartitionNodeType.VALUE, |
| token.getStartMark().getIndex() ); |
| exitNode(token.getEndMark().getIndex(), 0); |
| continue; |
| |
| // case Error: |
| // throw new IllegalStateException(); |
| } |
| } |
| } |
| |
| private void checkComment(final int offset) { |
| while (!this.comments.isEmpty()) { |
| final Pos pos= this.comments.getFirst(); |
| if (pos.getStartOffset() >= offset) { |
| break; |
| } |
| this.comments.removeFirst(); |
| final TreePartitionNode commentNode= this.scan.add(YamlPartitionNodeType.COMMENT_LINE, |
| this.node, pos.getStartOffset(), 0 ); |
| this.scan.expand(commentNode, pos.getEndOffset(), 0, true); |
| } |
| } |
| |
| protected void handleEOF(final YamlPartitionNodeType type) { |
| this.scan.expand(this.node, this.scan.getEndOffset(), 0, true); |
| } |
| |
| } |