| /******************************************************************************* |
| * Copyright (c) 2005, 2016 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.internal.parser; |
| |
| import java.io.CharArrayReader; |
| import java.io.StringReader; |
| import java.util.Arrays; |
| import java.util.StringTokenizer; |
| |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.dltk.ast.ASTNode; |
| import org.eclipse.dltk.ast.declarations.ModuleDeclaration; |
| import org.eclipse.dltk.ast.parser.AbstractSourceParser; |
| import org.eclipse.dltk.ast.references.ConstantReference; |
| import org.eclipse.dltk.ast.statements.Block; |
| import org.eclipse.dltk.compiler.env.IModuleSource; |
| import org.eclipse.dltk.compiler.env.ModuleSource; |
| import org.eclipse.dltk.compiler.problem.AbstractProblemReporter; |
| import org.eclipse.dltk.compiler.problem.IProblem; |
| import org.eclipse.dltk.compiler.problem.IProblemReporter; |
| import org.eclipse.dltk.compiler.util.Util; |
| import org.eclipse.dltk.core.DLTKCore; |
| import org.eclipse.dltk.ruby.ast.FakeModuleDeclaration; |
| import org.eclipse.dltk.ruby.ast.RubyClassDeclaration; |
| import org.eclipse.dltk.ruby.ast.RubyModuleDeclaration; |
| import org.eclipse.dltk.ruby.core.utils.RubySyntaxUtils; |
| import org.eclipse.dltk.ruby.internal.parsers.jruby.DLTKRubyParser; |
| import org.eclipse.dltk.ruby.internal.parsers.jruby.RubyASTBuildVisitor; |
| import org.jruby.ast.Node; |
| import org.jruby.ast.visitor.NodeVisitor; |
| import org.jruby.parser.RubyParserResult; |
| |
| public class JRubySourceParser extends AbstractSourceParser { |
| |
| private static boolean silentState = true; |
| |
| public static boolean isSilentState() { |
| return silentState; |
| } |
| |
| /** |
| * This option allows parser to suppress errors and exceptions and in result |
| * generate possibly partially non-correct AST instead of failing with |
| * exception. For running parser tests this option are being set to |
| * <code>false</code>. |
| */ |
| public static void setSilentState(boolean s) { |
| silentState = s; |
| } |
| |
| private static final boolean TRACE_AST_JRUBY = Boolean.valueOf( |
| Platform.getDebugOption("org.eclipse.dltk.core/traceAST/jruby")) //$NON-NLS-1$ |
| .booleanValue(); |
| |
| private static final boolean TRACE_AST_DLTK = Boolean.valueOf( |
| Platform.getDebugOption("org.eclipse.dltk.core/traceAST/dltk")) //$NON-NLS-1$ |
| .booleanValue(); |
| |
| private final boolean[] errorState = new boolean[1]; |
| |
| private RubyParserResult parserResult; |
| |
| public RubyParserResult getParserResult() { |
| return parserResult; |
| } |
| |
| private class ProxyProblemReporter extends AbstractProblemReporter { |
| |
| private final IProblemReporter original; |
| |
| public ProxyProblemReporter(IProblemReporter original) { |
| super(); |
| this.original = original; |
| } |
| |
| @Override |
| public void reportProblem(IProblem problem) { |
| if (original != null) |
| original.reportProblem(problem); |
| if (problem.isError()) { |
| errorState[0] = true; |
| } |
| } |
| |
| } |
| |
| public JRubySourceParser() { |
| |
| } |
| |
| /** |
| * Should return visitor for creating ModuleDeclaration from JRuby's AST |
| * |
| * @param module |
| * @param content |
| * @return |
| */ |
| protected NodeVisitor getASTBuilderVisitor(ModuleDeclaration module, |
| char[] content) { |
| return new RubyASTBuildVisitor(module, content); |
| } |
| |
| @Override |
| public ModuleDeclaration parse(IModuleSource input, |
| IProblemReporter reporter) { |
| try { |
| DLTKRubyParser parser = new DLTKRubyParser(); |
| ProxyProblemReporter proxyProblemReporter = new ProxyProblemReporter( |
| reporter); |
| errorState[0] = false; |
| |
| final long sTime = TRACE_AST_DLTK ? System.currentTimeMillis() : 0; |
| final String fileName = input.getFileName() != null ? input |
| .getFileName() : Util.EMPTY_STRING; |
| char[] content = input.getContentsAsCharArray(); |
| char[] fixedContent = RubySpacedParensFixer |
| .fixSpacedParens(content); |
| Node node; |
| if (Arrays.equals(fixedContent, content) != true) { |
| // ssanders - Parse with reporter to collect parenthesis |
| // warnings |
| parser.parse(fileName, new CharArrayReader(content), |
| proxyProblemReporter); |
| // ssanders - However, use modified content to have corrected |
| // position info |
| node = parser.parse(fileName, |
| new CharArrayReader(fixedContent), null); |
| } else { |
| node = parser.parse(fileName, new CharArrayReader(content), |
| proxyProblemReporter); |
| } |
| final RubySourceFixer fixer = new RubySourceFixer(); |
| if (!parser.isSuccess() || errorState[0]) { |
| String content2 = fixer.fix1(String.valueOf(fixedContent)); |
| |
| Node node2 = parser.parse(fileName, new StringReader(content2), |
| null); |
| if (node2 != null) |
| node = node2; |
| else { |
| fixer.clearPositions(); |
| |
| content2 = fixer.fixUnsafe1(content2); |
| |
| node2 = parser.parse(fileName, new StringReader(content2), |
| null); |
| if (node2 != null) |
| node = node2; |
| else { |
| fixer.clearPositions(); |
| |
| content2 = fixer.fixUnsafe2(content2); |
| |
| node2 = parser.parse(fileName, new StringReader( |
| content2), new AbstractProblemReporter() { |
| |
| @Override |
| public void reportProblem(IProblem problem) { |
| if (DLTKCore.DEBUG) { |
| System.out |
| .println("JRubySourceParser.parse(): Fallback Parse Problem - fileName=" + fileName + //$NON-NLS-1$ |
| ", message=" //$NON-NLS-1$ |
| + problem.getMessage() |
| + ", line=" + problem.getSourceLineNumber()); //$NON-NLS-1$ |
| } |
| } |
| |
| }); |
| } |
| if (node2 != null) |
| node = node2; |
| else |
| fixer.clearPositions(); |
| } |
| content = content2.toCharArray(); |
| } |
| |
| ModuleDeclaration module = new ModuleDeclaration(content.length); |
| NodeVisitor visitor = getASTBuilderVisitor(module, content); |
| if (node != null) |
| node.accept(visitor); |
| |
| if (node != null) { |
| if (TRACE_AST_JRUBY || TRACE_AST_DLTK) |
| System.out.println("\n\nAST rebuilt\n"); //$NON-NLS-1$ |
| if (TRACE_AST_JRUBY) |
| System.out.println("JRuby AST:\n" + node.toString()); //$NON-NLS-1$ |
| if (TRACE_AST_DLTK) |
| System.out.println("DLTK AST:\n" + module.toString()); //$NON-NLS-1$ |
| } |
| |
| fixer.correctPositionsIfNeeded(module); |
| |
| if (TRACE_AST_DLTK) { |
| long eTime = System.currentTimeMillis(); |
| System.out.println("Parsing took " + (eTime - sTime) //$NON-NLS-1$ |
| + " ms"); //$NON-NLS-1$ |
| } |
| this.parserResult = parser.getParserResult(); |
| |
| if (!parser.isSuccess() && module.isEmpty()) { |
| module = new FakeModuleDeclaration(content.length); |
| minimumParse(content, module); |
| } |
| |
| return module; |
| } catch (Throwable t) { |
| if (DLTKCore.DEBUG) { |
| t.printStackTrace(); |
| } |
| if (isSilentState()) { |
| ModuleDeclaration mdl = new ModuleDeclaration(1); |
| return mdl; |
| } |
| throw new RuntimeException(t); |
| } |
| } |
| |
| public ModuleDeclaration parse(String source) { |
| return this.parse(new ModuleSource(source), null); |
| } |
| |
| /** |
| * Really basic parse to find the first class or module definition, the |
| * intent is that a module declaration has at least a type in it (if one |
| * exists or can be parsed). |
| * |
| * @param content |
| * @param md |
| */ |
| private static void minimumParse(char[] content, ModuleDeclaration md) { |
| StringTokenizer toker = new StringTokenizer(new String(content)); |
| while (toker.hasMoreTokens()) { |
| String token = toker.nextToken(); |
| if (token.equals("class") || token.equals("module")) { //$NON-NLS-1$ //$NON-NLS-2$ |
| String className = toker.nextToken(); |
| |
| if (RubySyntaxUtils.isValidClass(className)) { |
| String source = new String(content); |
| // TODO(mhowe): Make position calculation more robust |
| int indexOf = source.indexOf(className); |
| int nameEnd = indexOf + className.length(); |
| RubyModuleDeclaration type; |
| ASTNode nameNode = new ConstantReference(indexOf, nameEnd, |
| className); |
| Block bodyBlock = new Block(indexOf + nameEnd, source |
| .length() - 1); |
| if (token.equals("class")) { //$NON-NLS-1$ |
| type = new RubyClassDeclaration(null, nameNode, |
| bodyBlock, indexOf, source.length() - 1); |
| } else |
| type = new RubyModuleDeclaration(nameNode, bodyBlock, |
| indexOf, source.length() - 1); |
| md.addStatement(type); |
| if (toker.nextToken().equals("<")) { //$NON-NLS-1$ |
| String superClass = toker.nextToken(); |
| if (RubySyntaxUtils.isValidClass(superClass)) { |
| indexOf = source.indexOf(className); |
| type.addSuperClass(new ConstantReference(indexOf, |
| indexOf + superClass.length(), superClass)); |
| } |
| } |
| return; |
| } |
| } |
| } |
| } |
| |
| } |