#!/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, required=True, 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
           #     modifiers.
           #   - A space character.
           #   - Capture the function name (\w*).
           #   - An opening parenthesis '(', indicating the start of the parameter
           #     list.
           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")