#!/usr/bin/env python3
#####
# asy-list.py
#
# Build asy-keywords.el from a list of Asymptote global functions and
# variables. This script reads definitions from 'camp.l' and writes Emacs Lisp
# code to 'asy-keywords.el'.
#
#####

import argparse
import re
import textwrap

parser = argparse.ArgumentParser()
parser.add_argument(
    "--asy-list-file", type=str, required=True, help="Path to the asy list file"
)
parser.add_argument(
    "--revision", type=str, default="", help="Revision identifier"
)
parser.add_argument(
    "--output-file", type=str, required=True, help="Path to output file"
)
args = parser.parse_args()

# Open the file 'asy-keywords.el' for writing.
with open(args.output_file, "w", encoding="utf-8") as keywords:

    # Write header information to 'asy-keywords.el'.
    # This includes comments and the definition of 'asy-keywords-version' using a
    # command-line argument.
    keywords.write(textwrap.dedent(f"""\
            ;;
            ;; This file is automatically generated by asy-list.py.
            ;; Changes will be overwritten.
            ;;
            (defvar asy-keywords-version "{args.revision}")

            """))

    # Define a function 'add' that adds a keyword to the output file.
    def add(keyword):
        keywords.write(keyword + " ")

    # Write the beginning of the Emacs Lisp definition for 'asy-keyword-name'.
    keywords.write("(defvar asy-keyword-name '(\n")

    # Open the file 'camp.l' for reading.
    with open("camp.l", "r", encoding="utf-8") as camp:

        # Read lines from 'camp.l' until reaching a line that contains only '%%'.
        for line in camp:
            if re.search(r"^%%\s*$", line):
                break

        # Continue reading lines from 'camp.l' after the '%%' line.
        for line in camp:
            if re.search(r"^%%\s*$", line):
                # Exit the loop when a second '%%' line is found, indicating the end of
                # the section.
                break
            # Match lines that start with a word (the keyword) followed by optional
            # whitespace and a '{'.
            match = re.search(r"^(\w+)\s*\{", line)
            if match:
                # Write the keyword followed by a space.
                keywords.write(match.group(1) + " ")

    # Open an input file specified in the command-line arguments.
    with open(args.asy_list_file, "r", encoding="utf-8") as asylist:

        # Lists to store types, functions, and variables found in the file.
        types = []  # List to hold data types.
        functions = []  # List to hold function names.
        variables = []  # List to hold variable names.

        # Read each line from the file handle asylist.
        for line in asylist:
            # Match lines that define functions.
            # The pattern looks for:
            #   - An optional word (\w*) representing the return type.
            #   - Any number of non-space characters ([^ ]*), which may include
            #     permission modifiers other than private
            #   - A space character.
            #   - Capture the function name (\w*).
            #   - An opening parenthesis '(', indicating the start of the parameter
            #     list.
            line = re.sub(r"(^|\w*)(restricted|public)(?=\w*|$)", "", line).strip()
            matchFun = re.search(r"^(\w*)[^ ]* (\w*)\(", line)
            if matchFun:
                types.append(matchFun.group(1))
                functions.append(matchFun.group(2))
            # Match lines that declare variables.
            # The pattern looks for:
            #   - Any non-space characters before a space ([^ ]*), representing the
            #     type.
            #   - A space character.
            #   - Capture the variable name (\w*).
            #   - A semicolon ';', indicating the end of the declaration.
            matchVarDec = re.search(r"^([^ ]*) (\w*);", line)
            if matchVarDec:
                variables.append(matchVarDec.group(2))

    # Remove duplicates and sort the lists.
    types = sorted(set(types))
    functions = sorted(set(functions))
    variables = sorted(set(variables))

    # Write the closing parentheses for the 'asy-keyword-name' definition in the
    # output file.
    keywords.write("))\n\n")

    # Write the beginning of the 'asy-type-name' definition to the output file.
    keywords.write("(defvar asy-type-name '(\n")

    # Write each type from types to the output file.
    for t in types:
        keywords.write(t + " ")  # Write the type followed by a space.

    # Write the closing parentheses for the 'asy-type-name' definition.
    keywords.write("))\n\n")

    # Write the beginning of the 'asy-function-name' definition to the output
    # file.
    keywords.write("(defvar asy-function-name '(\n")

    # Write each function name from functions to the output file.
    for f in functions:
        keywords.write(f + " ")  # Write the function name followed by a space.

    # Write the closing parentheses for the 'asy-function-name' definition.
    keywords.write("))\n\n")

    # Write the beginning of the 'asy-variable-name' definition to the output
    # file.
    keywords.write("(defvar asy-variable-name '(\n")

    # Write each variable name from variables to the output file.
    for v in variables:
        keywords.write(v + " ")  # Write the variable name followed by a space.

    # Write the closing parentheses for the 'asy-variable-name' definition.
    keywords.write("))\n")
