| /*=============================================================================# |
| # Copyright (c) 2012, 2018 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.internal.docmlet.tex.core.model; |
| |
| import static org.eclipse.statet.docmlet.tex.core.ITexProblemConstants.MASK_12; |
| import static org.eclipse.statet.docmlet.tex.core.ast.ITexAstStatusConstants.STATUS2_ENV_MISSING_NAME; |
| import static org.eclipse.statet.docmlet.tex.core.ast.ITexAstStatusConstants.STATUS2_ENV_NOT_CLOSED; |
| import static org.eclipse.statet.docmlet.tex.core.ast.ITexAstStatusConstants.STATUS2_ENV_NOT_OPENED; |
| import static org.eclipse.statet.docmlet.tex.core.ast.ITexAstStatusConstants.STATUS2_GROUP_NOT_CLOSED; |
| import static org.eclipse.statet.docmlet.tex.core.ast.ITexAstStatusConstants.STATUS2_GROUP_NOT_OPENED; |
| import static org.eclipse.statet.docmlet.tex.core.ast.ITexAstStatusConstants.STATUS2_MATH_NOT_CLOSED; |
| import static org.eclipse.statet.docmlet.tex.core.ast.ITexAstStatusConstants.STATUS2_VERBATIM_INLINE_C_MISSING; |
| import static org.eclipse.statet.docmlet.tex.core.ast.ITexAstStatusConstants.STATUS2_VERBATIM_INLINE_NOT_CLOSED; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.statet.jcommons.text.core.TextLineInformation; |
| |
| import org.eclipse.statet.ecommons.runtime.core.util.MessageBuilder; |
| |
| import org.eclipse.statet.docmlet.tex.core.ast.Comment; |
| import org.eclipse.statet.docmlet.tex.core.ast.ControlNode; |
| import org.eclipse.statet.docmlet.tex.core.ast.Dummy; |
| import org.eclipse.statet.docmlet.tex.core.ast.Embedded; |
| import org.eclipse.statet.docmlet.tex.core.ast.Environment; |
| import org.eclipse.statet.docmlet.tex.core.ast.Group; |
| import org.eclipse.statet.docmlet.tex.core.ast.Label; |
| import org.eclipse.statet.docmlet.tex.core.ast.Math; |
| import org.eclipse.statet.docmlet.tex.core.ast.SourceComponent; |
| import org.eclipse.statet.docmlet.tex.core.ast.TexAstNode; |
| import org.eclipse.statet.docmlet.tex.core.ast.TexAstVisitor; |
| import org.eclipse.statet.docmlet.tex.core.ast.Text; |
| import org.eclipse.statet.docmlet.tex.core.ast.Verbatim; |
| import org.eclipse.statet.docmlet.tex.core.model.ITexSourceUnit; |
| import org.eclipse.statet.docmlet.tex.core.model.TexModel; |
| import org.eclipse.statet.ltk.core.SourceContent; |
| import org.eclipse.statet.ltk.issues.core.BasicProblem; |
| import org.eclipse.statet.ltk.issues.core.Problem; |
| import org.eclipse.statet.ltk.issues.core.ProblemRequestor; |
| import org.eclipse.statet.ltk.model.core.elements.ISourceUnit; |
| |
| |
| public class LtxProblemAstVisitor extends TexAstVisitor { |
| |
| |
| private static final int ENV_LABEL_LIMIT= 20; |
| private static final int BUFFER_SIZE= 100; |
| |
| |
| private ISourceUnit currentUnit; |
| private String currentText; |
| private TextLineInformation currentLines; |
| private ProblemRequestor currentRequestor; |
| |
| private final MessageBuilder messageBuilder= new MessageBuilder(); |
| private final List<Problem> problemBuffer= new ArrayList<>(BUFFER_SIZE); |
| |
| |
| public void run(final ITexSourceUnit su, final SourceContent content, |
| final TexAstNode node, final ProblemRequestor requestor) { |
| try { |
| this.currentUnit= su; |
| this.currentText= content.getText(); |
| this.currentLines= content.getLines(); |
| |
| this.currentRequestor= requestor; |
| node.acceptInTex(this); |
| if (this.problemBuffer.size() > 0) { |
| this.currentRequestor.acceptProblems(TexModel.LTX_TYPE_ID, this.problemBuffer); |
| } |
| } |
| catch (final InvocationTargetException e) {} |
| finally { |
| this.problemBuffer.clear(); |
| this.currentUnit= null; |
| this.currentRequestor= null; |
| } |
| } |
| |
| |
| @Override |
| public void visit(final SourceComponent node) throws InvocationTargetException { |
| node.acceptInTexChildren(this); |
| } |
| |
| @Override |
| public void visit(final Group node) throws InvocationTargetException { |
| final int code= (node.getStatusCode() & MASK_12); |
| if (code == STATUS2_GROUP_NOT_CLOSED) { |
| if (node.getParent() instanceof ControlNode) { |
| addProblem(Problem.SEVERITY_ERROR, code, (node.getText() == "{") ? //$NON-NLS-1$ |
| ProblemMessages.Ast_ReqArgument_NotClosed_message : |
| ProblemMessages.Ast_OptArgument_NotClosed_Opt_message, |
| node.getStartOffset(), node.getStartOffset() + 1 ); |
| } |
| else { |
| addProblem(Problem.SEVERITY_ERROR, code, (node.getText() == "{") ? //$NON-NLS-1$ |
| ProblemMessages.Ast_CurlyBracket_NotClosed_message : |
| ProblemMessages.Ast_SquareBracket_NotClosed_message, |
| node.getStartOffset(), node.getStartOffset() + 1 ); |
| } |
| } |
| |
| node.acceptInTexChildren(this); |
| } |
| |
| @Override |
| public void visit(final Environment node) throws InvocationTargetException { |
| final int code= (node.getStatusCode() & MASK_12); |
| switch (code) { |
| case STATUS2_ENV_NOT_CLOSED: { |
| final TexAstNode beginNode= node.getBeginNode(); |
| addProblem(Problem.SEVERITY_ERROR, code, |
| this.messageBuilder.bind(ProblemMessages.Ast_Env_NotClosed_message, |
| limit(node.getText(), ENV_LABEL_LIMIT) ), |
| beginNode.getStartOffset(), beginNode.getEndOffset() ); |
| break; } |
| case STATUS2_MATH_NOT_CLOSED: { |
| final TexAstNode beginNode= node.getBeginNode(); |
| String c= node.getText(); |
| if (c == "[") { //$NON-NLS-1$ |
| c= "\\]"; //$NON-NLS-1$ |
| } |
| else if (c == "(") { //$NON-NLS-1$ |
| c= "\\)"; //$NON-NLS-1$ |
| } |
| else { |
| c= null; |
| } |
| if (c != null) { |
| addProblem(Problem.SEVERITY_ERROR, code, |
| this.messageBuilder.bind(ProblemMessages.Ast_Math_NotClosed_message, c), |
| beginNode.getStartOffset(), beginNode.getEndOffset() ); |
| } |
| break; } |
| } |
| |
| node.acceptInTexChildren(this); |
| } |
| |
| @Override |
| public void visit(final ControlNode node) throws InvocationTargetException { |
| final int code= (node.getStatusCode() & MASK_12); |
| switch (code) { |
| case STATUS2_ENV_MISSING_NAME: |
| addProblem(Problem.SEVERITY_ERROR, code, (node.getText() == "begin") ? //$NON-NLS-1$ |
| ProblemMessages.Ast_Env_MissingName_Begin_message : |
| ProblemMessages.Ast_Env_MissingName_End_message, |
| node.getStartOffset(), node.getEndOffset() ); |
| break; |
| case STATUS2_ENV_NOT_OPENED: |
| addProblem(Problem.SEVERITY_ERROR, code, |
| this.messageBuilder.bind(ProblemMessages.Ast_Env_NotOpened_message, |
| limit(node.getChild(0).getChild(0).getText(), ENV_LABEL_LIMIT) ), |
| node.getStartOffset(), node.getEndOffset() ); |
| break; |
| case STATUS2_VERBATIM_INLINE_C_MISSING: |
| addProblem(Problem.SEVERITY_ERROR, code, |
| ProblemMessages.Ast_Verbatim_MissingSep_message, |
| node.getEndOffset()-1, node.getEndOffset() ); |
| break; |
| } |
| |
| node.acceptInTexChildren(this); |
| } |
| |
| @Override |
| public void visit(final Text node) throws InvocationTargetException { |
| } |
| |
| @Override |
| public void visit(final Label node) throws InvocationTargetException { |
| } |
| |
| @Override |
| public void visit(final Math node) throws InvocationTargetException { |
| final int code= (node.getStatusCode() & MASK_12); |
| switch (code) { |
| case STATUS2_MATH_NOT_CLOSED: |
| addProblem(Problem.SEVERITY_ERROR, code, |
| this.messageBuilder.bind(ProblemMessages.Ast_Math_NotClosed_message, |
| node.getText() ), |
| node.getStartOffset(), node.getStartOffset() + node.getText().length() ); |
| break; |
| } |
| node.acceptInTexChildren(this); |
| } |
| |
| @Override |
| public void visit(final Verbatim node) throws InvocationTargetException { |
| final int code= (node.getStatusCode() & MASK_12); |
| switch (code) { |
| case STATUS2_VERBATIM_INLINE_NOT_CLOSED: |
| addProblem(Problem.SEVERITY_ERROR, code, |
| this.messageBuilder.bind(ProblemMessages.Ast_Verbatim_NotClosed_message, |
| new String(this.currentText.substring(node.getStartOffset() - 1, node.getStartOffset())) ), |
| node.getEndOffset() - 1, node.getEndOffset() ); |
| break; |
| } |
| } |
| |
| @Override |
| public void visit(final Comment node) throws InvocationTargetException { |
| } |
| |
| @Override |
| public void visit(final Dummy node) throws InvocationTargetException { |
| final int code= (node.getStatusCode() & MASK_12); |
| switch (code) { |
| case STATUS2_GROUP_NOT_OPENED: |
| addProblem(Problem.SEVERITY_ERROR, code, (node.getText() == "{") ? //$NON-NLS-1$ |
| ProblemMessages.Ast_CurlyBracket_NotOpened_message : |
| ProblemMessages.Ast_SquareBracket_NotOpened_message, |
| node.getStartOffset(), node.getStartOffset() + 1 ); |
| break; |
| } |
| } |
| |
| @Override |
| public void visit(final Embedded node) throws InvocationTargetException { |
| } |
| |
| |
| protected final String limit(final String label, final int limit) { |
| if (label != null && label.length() > limit) { |
| return label.substring(0, limit) + '…'; |
| } |
| return label; |
| } |
| |
| protected final void addProblem(final int severity, final int code, final String message, |
| int startOffset, int endOffset) { |
| if (startOffset < 0) { |
| startOffset= 0; |
| } |
| if (endOffset > this.currentText.length()) { |
| endOffset= this.currentText.length(); |
| } |
| this.problemBuffer.add(new BasicProblem(TexModel.LTX_TYPE_ID, severity, code, message, |
| startOffset, endOffset )); |
| if (this.problemBuffer.size() >= BUFFER_SIZE) { |
| this.currentRequestor.acceptProblems(TexModel.LTX_TYPE_ID, this.problemBuffer); |
| this.problemBuffer.clear(); |
| } |
| } |
| |
| } |