#! /usr/bin/env python

"""Generate pyglsl_parser/lexemes.py from glsl-parser/lexemes.h."""

from collections import OrderedDict
from enum import Enum
from keyword import iskeyword
import re
import os


# TODO(nicholasbishop): operators
class LexemeType(Enum):
    """GLSL lexeme categories."""
    TYPE = 0,
    KEYWORD = 1,
    TYPENAME = 2,


def lexeme_type_names():
    """Tuple of the LexemeType names."""
    return tuple(member.name for member in LexemeType)


def compile_pattern():
    """Compile the regex pattern used for parsing lexemes.h."""
    # Capture lexeme type
    group_one = r'({})'.format('|'.join(lexeme_type_names()))

    # Capture lexeme name
    group_two = r'\((\w+)\)'

    pattern = group_one + group_two
    return re.compile(pattern)


def parse_lexeme_line(regex, line):
    """Parse one line from the lexemes.h header.

    Returns a pair of (LexemeType, lexeme string), or None if the line
    can't be parsed.

    Example: "KEYWORD(float)" -> (LexemeType.KEYWORD, 'float')

    """
    match = regex.match(line)
    if match is not None:
        return match.groups()


def parse_lexemes(lexemes_file):
    """Parse the glsl-parser lexemes.h header.

    Returns a dictionary mapping LexemeType to a list of the
    corresponding lexemes.
    """
    regex = compile_pattern()
    lexemes = OrderedDict((name, []) for name in lexeme_type_names())

    for line in lexemes_file.readlines():
        lexeme = parse_lexeme_line(regex, line)
        if lexeme is not None:
            lexeme_type, name = lexeme
            lexemes[lexeme_type].append(name)

    return lexemes


def gen_enum_code(name, members):
    """Yield lines of Python code that define an enum."""
    yield '@unique'
    yield 'class {}(Enum):'.format(name)
    for index, member in enumerate(members):
        if iskeyword(member):
            member = '{}_'.format(member)
        yield '    {} = {}'.format(member, index)


def gen_lexemes_code(lexemes):
    """Generate the lines of code for lexemes.py."""
    yield '"""Autogenerated by gen_enums.py"""'
    yield 'from enum import Enum, unique'
    yield '# pylint: disable=invalid-name,missing-docstring'
    yield ''
    first = True
    for lexeme_type, lexemes in lexemes.items():
        if first:
            first = False
        else:
            yield ''
        yield from gen_enum_code(lexeme_type.capitalize(), lexemes)


def write_lines(lines, output_path):
    """Write out |lines| to |output_path|.

    The lines should not end in a newline character, that is added by
    this function.
    """
    with open(output_path, 'w') as output_file:
        for line in lines:
            output_file.write(line + '\n')


def main():
    """Parse the lexemes.h header and generate the Python enums."""
    script_dir = os.path.dirname(os.path.realpath(__file__))
    input_path = os.path.join(script_dir, 'glsl-parser/lexemes.h')
    output_path = os.path.join(script_dir, 'pyglsl_parser/lexemes.py')

    with open(input_path) as lexemes_file:
        lexemes = parse_lexemes(lexemes_file)

    code = gen_lexemes_code(lexemes)

    write_lines(code, output_path)


if __name__ == '__main__':
    main()

#! /usr/bin/env python

"""Generate pyglsl_parser/lexemes.py from glsl-parser/lexemes.h."""

from collections import OrderedDict
from enum import Enum
from keyword import iskeyword
import re
import os


# TODO(nicholasbishop): operators
class LexemeType(Enum):
    """GLSL lexeme categories."""
    TYPE = 0,
    KEYWORD = 1,
    TYPENAME = 2,


def lexeme_type_names():
    """Tuple of the LexemeType names."""
    return tuple(member.name for member in LexemeType)


def compile_pattern():
    """Compile the regex pattern used for parsing lexemes.h."""
    # Capture lexeme type
    group_one = r'({})'.format('|'.join(lexeme_type_names()))

    # Capture lexeme name
    group_two = r'\((\w+)\)'

    pattern = group_one + group_two
    return re.compile(pattern)


def parse_lexeme_line(regex, line):
    """Parse one line from the lexemes.h header.

    Returns a pair of (LexemeType, lexeme string), or None if the line
    can't be parsed.

    Example: "KEYWORD(float)" -> (LexemeType.KEYWORD, 'float')

    """
    match = regex.match(line)
    if match is not None:
        return match.groups()


def parse_lexemes(lexemes_file):
    """Parse the glsl-parser lexemes.h header.

    Returns a dictionary mapping LexemeType to a list of the
    corresponding lexemes.
    """
    regex = compile_pattern()
    lexemes = OrderedDict((name, []) for name in lexeme_type_names())

    for line in lexemes_file.readlines():
        lexeme = parse_lexeme_line(regex, line)
        if lexeme is not None:
            lexeme_type, name = lexeme
            lexemes[lexeme_type].append(name)

    return lexemes


def gen_enum_code(name, members):
    """Yield lines of Python code that define an enum."""
    yield '@unique'
    yield 'class {}(Enum):'.format(name)
    for index, member in enumerate(members):
        if iskeyword(member):
            member = '{}_'.format(member)
        yield '    {} = {}'.format(member, index)


def gen_lexemes_code(lexemes):
    """Generate the lines of code for lexemes.py."""
    yield '"""Autogenerated by gen_enums.py"""'
    yield 'from enum import Enum, unique'
    yield '# pylint: disable=invalid-name,missing-docstring'
    yield ''
    first = True
    for lexeme_type, lexemes in lexemes.items():
        if first:
            first = False
        else:
            yield ''
        yield from gen_enum_code(lexeme_type.capitalize(), lexemes)


def write_lines(lines, output_path):
    """Write out |lines| to |output_path|.

    The lines should not end in a newline character, that is added by
    this function.
    """
    with open(output_path, 'w') as output_file:
        for line in lines:
            output_file.write(line + '\n')


def main():
    """Parse the lexemes.h header and generate the Python enums."""
    script_dir = os.path.dirname(os.path.realpath(__file__))
    input_path = os.path.join(script_dir, 'glsl-parser/lexemes.h')
    output_path = os.path.join(script_dir, 'pyglsl_parser/lexemes.py')

    with open(input_path) as lexemes_file:
        lexemes = parse_lexemes(lexemes_file)

    code = gen_lexemes_code(lexemes)

    write_lines(code, output_path)


if __name__ == '__main__':
    main()

#! /usr/bin/env python Generate pyglsl_parser/lexemes.py from glsl-parser/lexemes.h. # TODO(nicholasbishop): operators GLSL lexeme categories. Tuple of the LexemeType names. Compile the regex pattern used for parsing lexemes.h. # Capture lexeme type # Capture lexeme name Parse one line from the lexemes.h header.      Returns a pair of (LexemeType, lexeme string), or None if the line     can't be parsed.      Example: "KEYWORD(float)" -> (LexemeType.KEYWORD, 'float') Parse the glsl-parser lexemes.h header.      Returns a dictionary mapping LexemeType to a list of the     corresponding lexemes. Yield lines of Python code that define an enum. Generate the lines of code for lexemes.py. Autogenerated by gen_enums.py Write out |lines| to |output_path|.      The lines should not end in a newline character, that is added by     this function. Parse the lexemes.h header and generate the Python enums.