/******************************************************************************* * Copyright (c) 2009-2017 CWI * 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: * * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI * * Tijs van der Storm - Tijs.van.der.Storm@cwi.nl * * Emilie Balland - (CWI) * * Anya Helene Bagge - A.H.S.Bagge@cwi.nl (Univ. Bergen) * * Mark Hills - Mark.Hills@cwi.nl (CWI) * * Arnold Lankamp - Arnold.Lankamp@cwi.nl * * Michael Steindorfer - Michael.Steindorfer@cwi.nl - CWI *******************************************************************************/ package org.rascalmpl.eclipse.editor; import java.util.Iterator; import java.util.Set; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.rascalmpl.eclipse.Activator; import org.rascalmpl.eclipse.IRascalResources; import org.rascalmpl.eclipse.nature.IWarningHandler; import org.rascalmpl.eclipse.nature.RascalMonitor; import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.interpreter.asserts.Ambiguous; import org.rascalmpl.interpreter.control_exceptions.Throw; import org.rascalmpl.interpreter.staticErrors.StaticError; import org.rascalmpl.library.lang.rascal.syntax.RascalParser; import org.rascalmpl.parser.Parser; import org.rascalmpl.parser.gtd.exception.ParseError; import org.rascalmpl.parser.gtd.result.action.IActionExecutor; import org.rascalmpl.parser.gtd.result.out.DefaultNodeFlattener; import org.rascalmpl.parser.uptr.UPTRNodeFactory; import org.rascalmpl.parser.uptr.action.NoActionExecutor; import org.rascalmpl.uri.ProjectURIResolver; import org.rascalmpl.uri.file.FileURIResolver; import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.exceptions.FactTypeUseException; import org.rascalmpl.values.uptr.ITree; import org.rascalmpl.values.uptr.TreeAdapter; import io.usethesource.impulse.language.Language; import io.usethesource.impulse.language.LanguageRegistry; import io.usethesource.impulse.model.ISourceProject; import io.usethesource.impulse.parser.IMessageHandler; import io.usethesource.impulse.parser.IParseController; import io.usethesource.impulse.parser.ISourcePositionLocator; import io.usethesource.impulse.services.IAnnotationTypeInfo; import io.usethesource.impulse.services.ILanguageSyntaxProperties; public class ParseController implements IParseController, IMessageHandlerProvider { protected IMessageHandler handler; protected ISourceProject project; protected IConstructor parseTree; protected ParseJob job; protected IPath path; protected Language language; protected IDocument document; protected Evaluator parser; protected IWarningHandler warnings; public IAnnotationTypeInfo getAnnotationTypeInfo() { return null; } public IMessageHandler getMessageHandler() { return handler; } public Object getCurrentAst() { return parseTree; } public void setCurrentAst(IConstructor parseTree) { this.parseTree = parseTree; } public Language getLanguage() { if (language == null) { language = LanguageRegistry.findLanguage("Rascal"); } return language; } public ISourcePositionLocator getSourcePositionLocator() { return new NodeLocator(); } public IPath getPath() { return path; } public ISourceProject getProject() { return project; } public ILanguageSyntaxProperties getSyntaxProperties() { return new RascalSyntaxProperties(); } public Iterator<Object> getTokenIterator(IRegion region) { return parseTree != null ? new TokenIterator(false, parseTree) : null; } @Override public void initialize(IPath filePath, ISourceProject project, IMessageHandler handler) { Assert.isTrue(filePath.isAbsolute() && project == null || !filePath.isAbsolute() && project != null); this.path = filePath; this.handler = handler; this.project = project; ISourceLocation location = getSourceLocation(); initParseJob(handler, location); } public ISourceLocation getSourceLocation() { if (project != null && path != null) { return ProjectURIResolver.constructProjectURI(project.getRawProject(), path); } else if (path != null) { return FileURIResolver.constructFileURI(path.toString()); } else { return null; } } protected void initParseJob(IMessageHandler handler, ISourceLocation location) { this.job = new ParseJob("Rascal parser", location, handler); } public IDocument getDocument() { return document; } public Object parse(IDocument doc, IProgressMonitor monitor) { if (doc == null) { return null; } this.document = doc; return parse(doc.get(), monitor); } public class ParseJob extends Job { protected final ISourceLocation uri; private Set<IResource> markedFiles; protected String input; public ITree parseTree = null; public ParseJob(String name, ISourceLocation uri, IMessageHandler handler) { super(name); this.uri = uri; } public void initialize(String input) { this.input = input; } protected void clearMarkers() { try { if (markedFiles != null) { for (IResource res : markedFiles) { res.deleteMarkers(IRascalResources.ID_RASCAL_MARKER, true, 0); } markedFiles = null; } } catch (CoreException e) { Activator.log("could not erase markers completely", e); } } private ITree parseModule(char[] input, ISourceLocation loc) { IActionExecutor<ITree> actions = new NoActionExecutor(); return new RascalParser().parse(Parser.START_MODULE, loc.getURI(), input, actions, new DefaultNodeFlattener<IConstructor, ITree, ISourceLocation>(), new UPTRNodeFactory(true)); } @Override protected IStatus run(IProgressMonitor monitor) { RascalMonitor rm = new RascalMonitor(monitor, warnings); clearMarkers(); rm.startJob("parsing", 500); parseTree = null; if (input == null || path == null || (path != null && !path.isAbsolute() && project == null)) { // may happen when project is deleted before Eclipse was started return null; } try { parseTree = parseModule(input.toCharArray(), uri); } catch (FactTypeUseException ftue) { Activator.getInstance().logException("parsing rascal failed", ftue); } catch (ParseError pe){ int offset = pe.getOffset(); if(offset > 0 && offset == input.length()) { --offset; } int k = Math.min(offset + 20, input.length()); String follow = input.substring(offset, k); StringBuffer msg = new StringBuffer(); boolean hasUni = false; msg.append(pe.toString()).append(" FOLLOWED BY: "); for(int i = 0; i < follow.length();i ++){ int c = follow.codePointAt(i); if((Character.isSpaceChar(c) && c != (int)' ' && c != (int)'\t' && c != (int)'\r' && c != (int)'\n')){ if(Character.charCount(c) == 1){ msg.append(String.format("\\u%04x", c)); } else { msg.append(String.format("\\U%06x", c)); } hasUni = true; } else msg.appendCodePoint(c); } if(hasUni) msg.append(" NOTE: unrecognized characters occur at \\u followed by a hexadecimal number"); setParseError(offset, pe.getLength(), pe.getBeginLine() + 1, pe.getBeginColumn(), pe.getEndLine() + 1, pe.getEndColumn(), msg.toString()); } catch (StaticError e) { ISourceLocation loc = e.getLocation(); if (loc.hasOffsetLength()) { setParseError(loc.getOffset(), loc.getLength(), loc.getBeginLine(), loc.getBeginColumn(), loc.getEndLine(), loc.getEndColumn(), e.getMessage()); } else { Activator.log("weird error during parsing", e); } } catch (Throw t) { ISourceLocation loc = t.getLocation(); setParseError(loc.getOffset(), loc.getLength(), loc.getBeginLine(), loc.getBeginColumn(), loc.getEndLine(), loc.getEndColumn(), t.getMessage()); } catch (Ambiguous e) { ISourceLocation loc = e.getLocation(); setParseError(loc.getOffset(), loc.getLength(), loc.getBeginLine(), loc.getBeginColumn(), loc.getEndLine(), loc.getEndColumn(), e.getMessage()); // reparse with raw rascal parser to get the full forest Activator.log("unexpected ambiguity during parsing of Rascal module", e); } finally { rm.endJob(true); } return Status.OK_STATUS; } } @Override public Object parse(String input, IProgressMonitor monitor) { parseTree = null; try { job.initialize(input); job.schedule(); job.join(); parseTree = job.parseTree; return parseTree; } catch (InterruptedException e) { Activator.getInstance().logException("parser interrupted", e); } return null; } protected void setParseError(int offset, int length, int beginLine, int beginColumn, int endLine, int endColumn, String message){ if(offset >= 0){ handler.handleSimpleMessage(message, offset, offset + ((length == 0) ? 0 : length - 1), beginColumn, endColumn, beginLine, endLine); }else{ handler.handleSimpleMessage(message, 0, 0, 0, 0, 1, 1); } } public String getModuleName() { if (getCurrentAst() == null) { return null; } ITree top = TreeAdapter.getStartTop((ITree) getCurrentAst()); ITree header = TreeAdapter.getArg(top, "header"); ITree name = TreeAdapter.getArg(header, "name"); return TreeAdapter.yield(name).replaceAll("\\\\",""); } }