All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.sbaudoin.sonar.plugins.shellcheck.lexer.AbstractBashLexer Maven / Gradle / Ivy

There is a newer version: 2.5.0
Show newest version
/**
 * Copyright (c) 2018-2019, Sylvain Baudoin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.sbaudoin.sonar.plugins.shellcheck.lexer;

import java.io.*;
import java.util.ArrayDeque;

/**
 * Abstract class the JFlex Bash lexer will extend to inherit methods required to properly
 * analyze Bash scripts
 *
 * Heavily inspired by IntelliJ IDEA Bash Support plugin
 * @see the IntelliJ IDEA Bash Support plugin
 */
public abstract class AbstractBashLexer {
    private final ArrayDeque lastStates = new ArrayDeque<>(25);
    private boolean inHereString = false;
    private final StringLexingstate string = new StringLexingstate();
    private final HeredocLexingState heredocState = new HeredocLexingState();
    // parameter expansion parsing states
    private boolean paramExpansionHash = false;
    private boolean paramExpansionWord = false;
    private boolean paramExpansionOther = false;
    private int openParenths = 0;
    boolean isBash4 = true;
    //True if the parser is in the case body. Necessary for proper lexing of the IN keyword
    private boolean inCaseBody = false;
    //conditional expressions
    private boolean emptyConditionalCommand = false;

    /**
     * Create and return a Token of given type from start with length
     * offset is added to start
     *
     * @param type the token type
     * @param start the offset at which the token starts
     * @param length the token length
     * @return a {@code Token} built from the passed parameters
     */
    protected Token token(TokenType type, int start, int length) {
        return new Token(type, start, length, yyline(), yycolumn());
    }

    /**
     * Create and return a {@code Token} of given type. The start offset is obtained from
     * {@link AbstractBashLexer#yychar()}, the length from {@link AbstractBashLexer#yylength()},
     * the line number from {@link AbstractBashLexer#yyline()} and the column number from
     * {@link AbstractBashLexer#yycolumn()}.
     *
     * @param type the token type
     * @return a {@code Token} of the given type
     */
    protected Token token(TokenType type) {
        return new Token(type, yychar(), yylength(), yyline(), yycolumn());
    }

    /**
     * This will be called to reset the the lexer.
     * This is created automatically by JFlex.
     *
     * @param reader a reader to the content to be analyzed
     */
    protected abstract void yyreset(Reader reader);

    /**
     * This is called to return the next Token from the input {@link Reader}
     *
     * @return next token, or null if no more tokens.
     * @throws IOException if the input reader cannot be read
     */
    protected abstract Token yylex() throws IOException;

    /**
     * Returns the character at position pos from the
     * matched text.
     * It is equivalent to {@code yytext().charAt(pos)}, but faster.
     *
     * @param pos the position of the character to fetch.
     *            A value from 0 to {@code yylength()-1}.
     * @return the character at position pos
     */
    protected abstract char yycharat(int pos);

    /**
     * Returns the length of the matched text region.
     * This method is automatically implemented by JFlex lexers.
     *
     * @return the length of the matched text region
     */
    protected abstract int yylength();

    /**
     * Returns the text matched by the current regular expression.
     * This method is automatically implemented by JFlex lexers.
     *
     * @return the text matched by the current regular expression
     */
    protected abstract String yytext();

    /**
     * Returns the char number from beginning of input stream.
     * This is NOT implemented by JFlex, so the code must be
     * added to create this and return the private yychar field.
     *
     * @return the char number from beginning of input stream
     */
    protected abstract int yychar();

    /**
     * Returns the start line number from beginning of input stream (starting from 0).
     * This is NOT implemented by JFlex, so the code must be
     * added to create this and return the private yyline field.
     *
     * @return the start line number from beginning of input stream (starting from 0)
     */
    protected abstract int yyline();

    /**
     * Returns the column number from beginning of the line (starting from 0).
     * This is NOT implemented by JFlex, so the code must be
     * added to create this and return the private yycolumn field
     *
     * @return the column number from beginning of the line (starting from 0)
     */
    protected abstract int yycolumn();

    /**
     * Enters a new lexical state.
     * This method is automatically implemented by JFlex lexers.
     *
     * @param newState the new nexical state
     */
    protected abstract void yybegin(int newState);

    /**
     * Returns the current lexical state
     *
     * @return the current lexical state
     */
    protected abstract int yystate();

    /**
     * Tells if the lexer is currently analyzing a in-here string
     *
     * @return {@code true} if the lexer is analyzing a in-here string, {@code false} if not
     */
    protected boolean isInHereStringContent() {
        return inHereString;
    }

    /**
     * Sets the in-here string state to {@code false}
     */
    protected void leaveHereStringContent() {
        inHereString = false;
    }

    /**
     * Goes back to the previous state of the lexer. If there
     * is no previous state then {@code YYINITIAL}, the initial state, is chosen.
     */
    protected void backToPreviousState() {
        // pop() will throw an exception if empty
        yybegin(lastStates.pop());
    }

    /**
     * Tells if the current lexical state is the passed state (possibly nesting/parent stated)
     *
     * @param state a lexical state to test
     * @return {@code true} if the passed state is in the current lexical state stack, {@code false} if not
     */
    protected boolean isInState(int state) {
        return (state == 0 && lastStates.isEmpty()) || lastStates.contains(state);
    }

    /**
     * Returns the {@code HeredocLexingState} object used by the lexer to deal with here-docs
     *
     * @return the {@code HeredocLexingState} object used by the lexer to deal with here-docs
     */
    protected HeredocLexingState heredocState() {
        return heredocState;
    }

    /**
     * Goes to the given state and stores the previous state on the stack of states.
     * This makes it possible to have several levels of lexing, e.g. for {@code $(( 1+ $(echo 3) ))}.
     *
     * @param newState the new state to go to
     */
    protected void goToState(int newState) {
        lastStates.push(yystate());
        yybegin(newState);
    }

    /**
     * Returns the {@code StringLexingState} object used by the lexer to deal with strings
     *
     * @return the {@code StringLexingState} object used by the lexer to deal with strings
     */
    protected StringLexingstate stringParsingState() {
        return string;
    }

    /**
     * Tells if an empty conditional command ({@code [ ]}) was met or not
     *
     * @return {@code true} if an empty conditional command ({@code [ ]}) was met, {@code false} if not
     */
    protected boolean isEmptyConditionalCommand() {
        return emptyConditionalCommand;
    }

    /**
     * Sets the flag that tracks empty conditional commands
     *
     * @param emptyConditionalCommand the value for the empty conditional command flag
     */
    protected void setEmptyConditionalCommand(boolean emptyConditionalCommand) {
        this.emptyConditionalCommand = emptyConditionalCommand;
    }

    /**
     * Decrements the open parenthesis counter
     */
    protected void decOpenParenthesisCount() {
        openParenths--;
    }

    /**
     * Increments the open parenthesis counter
     */
    protected void incOpenParenthesisCount() {
        openParenths++;
    }

    /**
     * Returns the open parenthesis counter, i.e. the parenthesis nesting level
     *
     * @return the open parenthesis counter
     */
    protected int openParenthesisCount() {
        return openParenths;
    }

    /**
     * Notifies that the lexer is entering a here-string content
     */
    protected void enterHereStringContent() {
        assert !inHereString : "inHereString must be false when entering a here string";

        inHereString = true;
    }

    /**
     * Tells if the lexer is currently considering a parameter expansion based on {@code #} (e.g. ${parameter##word})
     *
     * @return {@code true} if the lexer is currently analyzing parameter expansion with # or not ({@code false})
     */
    protected boolean isParamExpansionHash() {
        return paramExpansionHash;
    }

    /**
     * Sets the flag that tracks parameter expansion based on {@code #}
     *
     * @param paramExpansionHash the new value for the flag
     */
    protected void setParamExpansionHash(boolean paramExpansionHash) {
        this.paramExpansionHash = paramExpansionHash;
    }

    /**
     * Tells if the current lexer token is a parameter expansion word
     *
     * @return {@code true} if the current token is a parameter expansion word, {@code false} if not
     */
    protected boolean isParamExpansionWord() {
        return paramExpansionWord;
    }

    /**
     * Sets the flag that tracks parameter expansion words
     *
     * @param paramExpansionWord the new value for the flag
     */
    protected void setParamExpansionWord(boolean paramExpansionWord) {
        this.paramExpansionWord = paramExpansionWord;
    }

    /**
     * Tells if the current token is a parameter expansion marker other than {@code #} (e.g. {@code %})
     *
     * @return {@code true} if the current token is a parameter expansion other that a word, {@code false} if not
     */
    protected boolean isParamExpansionOther() {
        return paramExpansionOther;
    }

    /**
     * Sets the flag that tracks parameter expansion marker other than {@code #}
     *
     * @param paramExpansionOther the new value for the flag
     */
    protected void setParamExpansionOther(boolean paramExpansionOther) {
        this.paramExpansionOther = paramExpansionOther;
    }

    /**
     * Pops the passed state from the stack of nested states. Child states are also popped out.
     *
     * @param lastStateToPop the state to be popped
     */
    protected void popStates(int lastStateToPop) {
        if (yystate() == lastStateToPop) {
            backToPreviousState();
            return;
        }

        while (isInState(lastStateToPop)) {
            boolean finished = (yystate() == lastStateToPop);
            backToPreviousState();

            if (finished) {
                break;
            }
        }
    }

    /**
     * Tells if the lexer is applying the Bash 4 syntax to analyze the code
     *
     * @return {@code true} if the lexer is usgin the Bash 4 grammar, {@code false} if not
     */
    public boolean isBash4() {
        return isBash4;
    }

    /**
     * Tells if the lexer is currently in a case statement
     *
     * @return {@code true} if the lexer is in a case statement, {@code false} if not
     */
    protected boolean isInCaseBody() {
        return inCaseBody;
    }

    /**
     * Sets the flag that tells if the lexer is in a case statement
     *
     * @param inCaseBody the new value for the flag
     */
    protected void setInCaseBody(boolean inCaseBody) {
        this.inCaseBody = inCaseBody;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy