# Copyright (C) 2016 the V8 project authors. All rights reserved.
# This code is governed by the BSD license found in the LICENSE file.

def find_comments(source):
    '''Parse input string describing JavaScript source and yield dictionaries
    describing the JavaScript comments in the order they appear in the source.
    This includes comment patterns within string literals.

    Each dictionary defines the following attributes:

    - source: the source text of the comment
    - firstchar: the zero-indexed position of the token that begins the comment
    - lastchar: the zero-indexed position of the token that closes the comment
    - lineno: the zero-indexed offset of the line on which the comment appears
    - in_string: `False` if the comment is a true JavaScript comment, one of
                 '\'' (single quote), '"' (double quote), or '`' (back tick) if
                 the comment pattern appears within a string literal.
    '''
    in_string = False
    in_s_comment = False
    in_m_comment = False
    follows_escape = False
    comment = ''
    lineno = 0

    for idx in range(len(source)):
        if source[idx] == '\n':
            lineno += 1

        # Within comments and strings, any odd number of back-slashes begins an
        # escape sequence.
        if source[idx - 1] == '\\':
            follows_escape = not follows_escape
        else:
            follows_escape = False

        if in_s_comment:
            if source[idx] == '\n':
                in_s_comment = False
                yield dict(
                    source=comment[1:],
                    firstchar=idx - len(comment) - 1,
                    lastchar=idx,
                    in_string=in_string,
                    lineno=lineno)
                continue
        elif in_m_comment:
            if source[idx - 1] == '*' and source[idx] == '/':
                in_m_comment = False
                yield dict(
                    source=comment[1:-1],
                    firstchar=idx - len(comment) - 1,
                    lastchar=idx + 1,
                    in_string=in_string,
                    lineno=lineno)
                continue
        elif in_string:
            if source[idx] == in_string and not follows_escape:
                in_string = False
            elif source[idx] == '\n' and in_string != '`' and not follows_escape:
                in_string = False

        if in_m_comment or in_s_comment:
            comment += source[idx]
            continue

        in_m_comment = source[idx] == '/' and source[idx + 1] == '*'
        in_s_comment = source[idx] == '/' and source[idx + 1] == '/'

        if in_m_comment or in_s_comment:
            comment = ''
        elif source[idx] == '\'' or source[idx] == '"' or source[idx] == '`':
            in_string = source[idx]
