text: #!/usr/bin/env python3
  #
  # Copyright (c) 2017 Intel Corporation
  #
  # SPDX-License-Identifier: Apache-2.0
  
  import sys
  import re
  import argparse
  import os
  import json
  
  api_regex = re.compile(r'''
  __syscall\s+                    # __syscall attribute, must be first
  ([^(]+)                         # type and name of system call (split later)
  [(]                             # Function opening parenthesis
  ([^)]*)                         # Arg list (split later)
  [)]                             # Closing parenthesis
  ''', re.MULTILINE | re.VERBOSE)
  
  typename_regex = re.compile(r'(.*?)([A-Za-z0-9_]+)$')
  
  class SyscallParseException(Exception):
      pass
  
  
  def typename_split(item):
      if "[" in item:
          raise SyscallParseException("Please pass arrays to syscalls as pointers, unable to process '%s'"
                  % item)
  
      if "(" in item:
          raise SyscallParseException("Please use typedefs for function pointers")
  
      mo = typename_regex.match(item)
      if not mo:
          raise SyscallParseException("Malformed system call invocation")
  
      m = mo.groups()
      return (m[0].strip(), m[1])
  
  
  def analyze_fn(match_group, fn):
      func, args = match_group
  
      try:
          if args == "void":
              args = []
          else:
              args = [typename_split(a.strip()) for a in args.split(",")]
  
          func_type, func_name = typename_split(func)
      except SyscallParseException:
          sys.stderr.write("In declaration of %s\n" % func)
          raise
  
      sys_id = "K_SYSCALL_" + func_name.upper()
  
      if func_type == "void":
          suffix = "_VOID"
          is_void = True
      else:
          is_void = False
          if func_type in ["s64_t", "u64_t"]:
              suffix = "_RET64"
          else:
              suffix = ""
  
      is_void = (func_type == "void")
  
      # Get the proper system call macro invocation, which depends on the
      # number of arguments, the return type, and whether the implementation
      # is an inline function
      macro = "K_SYSCALL_DECLARE%d%s" % (len(args), suffix)
  
      # Flatten the argument lists and generate a comma separated list
      # of t0, p0, t1, p1, ... tN, pN as expected by the macros
      flat_args = [i for sublist in args for i in sublist]
      if not is_void:
          flat_args = [func_type] + flat_args
      flat_args = [sys_id, func_name] + flat_args
      argslist = ", ".join(flat_args)
  
      invocation = "%s(%s);" % (macro, argslist)
  
      handler = "_handler_" + func_name
  
      # Entry in _k_syscall_table
      table_entry = "[%s] = %s" % (sys_id, handler)
  
      return (fn, handler, invocation, sys_id, table_entry)
  
  
  def analyze_headers(base_path):
      ret = []
  
      for root, dirs, files in os.walk(base_path):
          for fn in files:
  
              # toolchain/common.h has the definition of __syscall which we
              # don't want to trip over
              path = os.path.join(root, fn)
              if not fn.endswith(".h") or path.endswith("toolchain/common.h"):
                  continue
  
              with open(path, "r", encoding="utf-8") as fp:
                  try:
                      result = [analyze_fn(mo.groups(), fn)
                                for mo in api_regex.finditer(fp.read())]
                  except Exception:
                      sys.stderr.write("While parsing %s\n" % fn)
                      raise
  
              ret.extend(result)
  
      return ret
  
  def parse_args():
      global args
      parser = argparse.ArgumentParser(description = __doc__,
              formatter_class = argparse.RawDescriptionHelpFormatter)
  
      parser.add_argument("-i", "--include", required=True,
              help="Base include directory")
      parser.add_argument("-j", "--json-file", required=True,
              help="Write system call prototype information as json to file")
      args = parser.parse_args()
  
  def main():
      parse_args()
  
      syscalls = analyze_headers(args.include)
  
      syscalls_in_json = json.dumps(
          syscalls,
          indent=4,
          sort_keys=True
      )
  
      # Check if the file already exists, and if there are no changes,
      # don't touch it since that will force an incremental rebuild
      path = args.json_file
      new = syscalls_in_json
      if os.path.exists(path):
          with open(path, 'r') as fp:
              old = fp.read()
  
          if new != old:
              with open(path, 'w') as fp:
                  fp.write(new)
      else:
          with open(path, 'w') as fp:
              fp.write(new)
  
  if __name__ == "__main__":
      main()
  