/******************************************************************************* * Copyright (c) 2015, 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Bruno Medeiros - initial API and implementation *******************************************************************************/ package melnorme.lang.tooling.parser; import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue; import java.util.Collections; import melnorme.lang.utils.parse.LexingUtils; import melnorme.lang.utils.parse.StringCharSource; import melnorme.utilbox.collections.ArrayList2; import melnorme.utilbox.core.CommonException; public class SourceLinesInfo { protected final String source; protected final ArrayList2<Integer> lines; // entries contain lineStart public SourceLinesInfo(String source) { this.source = source; this.lines = calculateLines(new StringCharSource(source)); } protected ArrayList2<Integer> calculateLines(StringCharSource parser) { ArrayList2<Integer> lines = new ArrayList2<>(); int lineStartOffset = 0; do { consumeNewLine(parser); lines.add(lineStartOffset); lineStartOffset = parser.getReadPosition(); } while(parser.hasCharAhead()); return lines; } protected void consumeNewLine(StringCharSource parser) { while(true) { String newlineSequence = LexingUtils.determineNewlineSequenceAt(parser, 0); if(newlineSequence != null) { parser.consumeAhead(newlineSequence); return; } parser.consume(); } } public String getSource() { return source; } /* ----------------- ----------------- */ public void validateOffset(int offset) throws CommonException { if(offset > source.length()) { throw CommonException.fromMsgFormat("Invalid offset {0}, it is out of bounds.", offset); } } public int getNumberOfLines() { return lines.size(); } public int getOffsetForLine(int lineIndex) { assertTrue(lineIndex >= 0 && lineIndex < lines.size()); return lines.get(lineIndex); } public int getLineForOffset(int offset) throws CommonException { validateOffset(offset); int binarySearchResult = Collections.binarySearch(lines, offset); return binarySearchResult >= 0 ? binarySearchResult : -(binarySearchResult + 1) -1; } public int getLineStartForOffset(int offset) throws CommonException { return getOffsetForLine(getLineForOffset(offset)); } public int getColumnForOffset(int offset) throws CommonException { return offset - getLineStartForOffset(offset); } /* ----------------- ----------------- */ public int getValidatedOffset_1(int line_1, int column_1) throws CommonException { if(line_1 < 1) { throw new CommonException("Invalid line number: " + line_1); } if(column_1 < 1) { throw new CommonException("Invalid column number: " + line_1); } int lineIndex = line_1 - 1; int columnIndex = column_1 - 1; if(lineIndex >= lines.size()) { throw CommonException.fromMsgFormat("Invalid line: {0} is over the max bound: {1}.", line_1, lines.size()); } return getValidateOffset_do(lineIndex, columnIndex); } public int getValidatedOffset_0(int line_0, int column_0) throws CommonException { if(line_0 < 0) { throw new CommonException("Invalid line number: " + line_0); } if(column_0 < 0) { throw new CommonException("Invalid column number: " + line_0); } if(line_0 >= lines.size()) { throw CommonException.fromMsgFormat("Invalid line: {0} is over the max bound: {1}.", line_0, lines.size()); } return getValidateOffset_do(line_0, column_0); } protected int getValidateOffset_do(int lineIndex, int columnIndex) throws CommonException { int offset = getOffsetForLine(lineIndex) + columnIndex; if(lineIndex + 1 < lines.size()) { if(offset >= getOffsetForLine(lineIndex + 1)) { throw new CommonException("Invalid column, out of bounds."); } } if(offset > source.length()) { throw new CommonException("Invalid line+column, out of bounds."); } return offset; } /* ----------------- ----------------- */ public int getIdentifierAt(int validatedOffset) { StringCharSource parser = new StringCharSource(source); parser.consumeAmount(validatedOffset); return LexingUtils.matchJavaIdentifier(parser); } }