blob: a99e8a937d18e2666486069ba9e3e123a8eb0f82 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2007 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
*
*******************************************************************************/
package org.eclipse.dltk.ruby.core.utils;
import java.util.regex.Pattern;
import org.eclipse.dltk.core.ISourceRange;
import org.eclipse.dltk.internal.core.SourceRange;
public class RubySyntaxUtils {
public static final String ARRAY_GET_METHOD = "[]".intern(); //$NON-NLS-1$
public static final String ARRAY_PUT_METHOD = "[]=".intern(); //$NON-NLS-1$
// FIXME Kalugin-WTF get the actual list from Andrey Tarantsov
private static final String[] operatorMethods =
{"[]", "[]=", "**", "!", "~", "+", "-", "*", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
"/", "%", "<<", ">>", "&", "^", "|", "<=", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
">", "<", ">=", "<=>", "==", "===", "!=", "=~", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
"+@", "-@"}; //$NON-NLS-1$ //$NON-NLS-2$
/**
* If position is located inside a operator method, returns full
* region correcponding to that operator.
* @param contents
* @param pos
* @return range, if position is inside op., <code>null</code> if not
*/
public static ISourceRange insideMethodOperator (String contents, int pos) {
for (int i = 0; i < operatorMethods.length; i++) {
String op = operatorMethods[i];
int opLength = op.length();
for (int j = 0; j < opLength; j++) {
try {
int start = pos - j;
int end = pos - j + opLength;
String piece = contents.substring(start, end);
if (!piece.equals(op))
continue;
return new SourceRange(start, opLength);
} catch (IndexOutOfBoundsException e) {
continue;
}
}
}
return null;
}
// FIXME Kalugin-WTF what about ? and !
// Re: they are specially processed, ?,!,=(yes, you forgot =) is not a name chars
public static boolean isNameChar (char c) {
return (Character.isLetterOrDigit(c) || c == '_');
}
/**
* Tries to find name enclosing given position.
* See isRubyName method for "name" definion.
* @param contents
* @param pos
* @return
*/
public static ISourceRange getEnclosingName (CharSequence contents, int pos) {
if (pos < 0 || pos >= contents.length())
return null;
int start = pos - 1;
int end = pos;
while (start >= 0 && isNameChar(contents.charAt(start)))
start--;
if (start > 0) {
if (contents.charAt(start) == '@') {
start--;
if (start > 0 && contents.charAt(start) == '@')
start--;
} else if (contents.charAt(start) == '$')
start--;
}
end = start + 1;
if (end < contents.length() && contents.charAt(end) == '@') {
end++;
if (end < contents.length() && contents.charAt(end) == '@')
end++;
} else
if (end < contents.length() && contents.charAt(end) == '$')
end++;
while (end < contents.length() && isNameChar(contents.charAt(end)))
end++;
if (end < contents.length()) {
char c = contents.charAt(end);
if (c == '?' || c == '!' || c == '='){
end++;
} else {
}
}
int actualStart = start + 1;
int actualEnd = end - 1;
if (actualStart > actualEnd)
return null;
return new SourceRange(actualStart, actualEnd - actualStart + 1);
}
/**
* Checks whether given name is an Ruby name.
* It's name if it's operator or matches folling regexp:
* <pre>^(@{0,2}|\\$)[_a-zA-Z0-9]+[\\?!=]?$</pre>
* @param str
* @return
*/
public static boolean isRubyName (String str) {
for (int i = 0; i < operatorMethods.length; i++) {
if (operatorMethods[i].equals(str))
return true;
}
return str.matches("^(@{0,2}|\\$)[_a-zA-Z0-9]+[\\?!=]?$"); //$NON-NLS-1$
}
private static final Pattern RE_METHOD_NAME = Pattern
.compile("[_a-zA-Z0-9]+[\\?!=]?"); //$NON-NLS-1$
/**
* Checks whether given name is a Ruby method name.
*
* @param str
* @return
*/
public static boolean isRubyMethodName(String str) {
return RE_METHOD_NAME.matcher(str).matches();
}
public static boolean isLessStrictIdentifierCharacter(char ch) {
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '@' || ch == '$';
}
public static boolean isStrictIdentifierCharacter(char ch) {
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '@' || ch == '$';
}
public static boolean isIdentifierCharacter(char ch) {
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '?' || ch == '!'
|| ch == '@' || ch == '$';
}
public static int getInclusiveStartOfIdentifierEndingAt(CharSequence document, int inclusiveEndOffset) {
char ch = document.charAt(inclusiveEndOffset);
if (!isIdentifierCharacter(ch))
return -1;
for (int offset = inclusiveEndOffset - 1; offset >= 0; offset--)
if (!isIdentifierCharacter(document.charAt(offset)))
return offset + 1;
return 0;
}
public static boolean isValidRegexpModifier(char ch) {
switch (ch) {
case 'i':
case 'x':
case 'm':
case 'o':
case 'n':
case 'e':
case 's':
case 'u':
return true;
default:
return false;
}
}
public static boolean isValidPercentStringStarter(char ch) {
switch (ch) {
case 'Q':
case 'q':
case 'W':
case 'w':
case 'x':
case 'r':
case 's':
return true;
default:
return false;
}
}
public static char getPercentStringTerminator(char leader) {
switch (leader) {
case '(':
return ')';
case '[':
return ']';
case '{':
return '}';
case '<':
return '>';
case '!':
case '@':
case '#':
case '$':
case '%':
case '^':
case '&':
case '*':
case ')':
case '-':
case '_':
case '=':
case '+':
case ']':
case '}':
case ';':
case ':':
case '\'':
case '"':
case '\\':
case '|':
case ',':
case '.':
case '>':
case '/':
case '?':
case '`':
case '~':
return leader;
default:
return (char) 0;
}
}
public static int skipWhitespaceForward(char[] content, final int offset) {
return skipWhitespaceForward(content, offset, content.length);
}
public static int skipWhitespaceForward(char[] content, final int offset, final int end) {
for (int result = offset; result < end; result++) {
if (result < 0 || result >= content.length)
break;
if (!isWhitespace(content[result]))
return result;
}
return -1;
}
public static boolean isWhitespace(char ch) {
switch(ch) {
case ' ':
case '\t':
case '\n':
case '\r':
return true;
default:
return false;
}
}
public static boolean isNonNewLineWhitespace(char ch) {
switch(ch) {
case ' ':
case '\t':
return true;
default:
return false;
}
}
public static boolean isRubyOperator(String op) {
for (int i = 0; i < operatorMethods.length; i++) {
if (op.equals(operatorMethods[i]))
return true;
}
return false;
}
public static boolean isValidConstant(String input) {
boolean result = input.matches("[A-Z][a-zA-Z0-9_]*"); //$NON-NLS-1$
return result;
}
public static boolean isValidClass(String input) {
boolean result = false;
if (input.indexOf("::") != -1) { //$NON-NLS-1$
String[] tokens = input.split("::"); //$NON-NLS-1$
for (int cnt = 0, max = tokens.length; cnt < max; cnt++) {
result = isValidConstant(tokens[cnt]);
if (result != true) {
break;
}
}
} else {
result = isValidConstant(input);
}
return result;
}
}