blob: f2a8bb26fbefc0f8fdc058bac59b20f4461518ea [file] [log] [blame]
/*=============================================================================#
# 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);
}
}