From 67a4094eea3de8353cd9122e5d9e1a8ff1c64e9d Mon Sep 17 00:00:00 2001
From: Sylvain <[EMAIL REDACTED]>
Date: Wed, 7 Dec 2022 23:16:10 +0100
Subject: [PATCH] Rewrite + gendynapi into python
---
src/dynapi/gendynapi.pl | 158 --------------
src/dynapi/gendynapi.py | 449 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 449 insertions(+), 158 deletions(-)
delete mode 100755 src/dynapi/gendynapi.pl
create mode 100755 src/dynapi/gendynapi.py
diff --git a/src/dynapi/gendynapi.pl b/src/dynapi/gendynapi.pl
deleted file mode 100755
index e5300db1e75c..000000000000
--- a/src/dynapi/gendynapi.pl
+++ /dev/null
@@ -1,158 +0,0 @@
-#!/usr/bin/perl -w
-
-# Simple DirectMedia Layer
-# Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
-#
-# This software is provided 'as-is', without any express or implied
-# warranty. In no event will the authors be held liable for any damages
-# arising from the use of this software.
-#
-# Permission is granted to anyone to use this software for any purpose,
-# including commercial applications, and to alter it and redistribute it
-# freely, subject to the following restrictions:
-#
-# 1. The origin of this software must not be misrepresented; you must not
-# claim that you wrote the original software. If you use this software
-# in a product, an acknowledgment in the product documentation would be
-# appreciated but is not required.
-# 2. Altered source versions must be plainly marked as such, and must not be
-# misrepresented as being the original software.
-# 3. This notice may not be removed or altered from any source distribution.
-
-# WHAT IS THIS?
-# When you add a public API to SDL, please run this script, make sure the
-# output looks sane (git diff, it adds to existing files), and commit it.
-# It keeps the dynamic API jump table operating correctly.
-
-# If you wanted this to be readable, you shouldn't have used perl.
-
-use warnings;
-use strict;
-use File::Basename;
-
-chdir(dirname(__FILE__) . '/../..');
-my $sdl_dynapi_procs_h = "src/dynapi/SDL_dynapi_procs.h";
-my $sdl_dynapi_overrides_h = "src/dynapi/SDL_dynapi_overrides.h";
-my $sdl_dynapi_sym = "src/dynapi/SDL_dynapi.sym";
-
-my %existing = ();
-if (-f $sdl_dynapi_procs_h) {
- open(SDL_DYNAPI_PROCS_H, '<', $sdl_dynapi_procs_h) or die("Can't open $sdl_dynapi_procs_h: $!\n");
- while (<SDL_DYNAPI_PROCS_H>) {
- if (/\ASDL_DYNAPI_PROC\(.*?,(.*?),/) {
- $existing{$1} = 1;
- }
- }
- close(SDL_DYNAPI_PROCS_H)
-}
-
-open(SDL_DYNAPI_PROCS_H, '>>', $sdl_dynapi_procs_h) or die("Can't open $sdl_dynapi_procs_h: $!\n");
-open(SDL_DYNAPI_OVERRIDES_H, '>>', $sdl_dynapi_overrides_h) or die("Can't open $sdl_dynapi_overrides_h: $!\n");
-
-open(SDL_DYNAPI_SYM, '<', $sdl_dynapi_sym) or die("Can't open $sdl_dynapi_sym: $!\n");
-read(SDL_DYNAPI_SYM, my $sdl_dynapi_sym_contents, -s SDL_DYNAPI_SYM);
-close(SDL_DYNAPI_SYM);
-
-opendir(HEADERS, 'include/SDL3') or die("Can't open include dir: $!\n");
-while (my $d = readdir(HEADERS)) {
- next if not $d =~ /\.h\Z/;
- my $header = "include/SDL3/$d";
- open(HEADER, '<', $header) or die("Can't open $header: $!\n");
- while (<HEADER>) {
- chomp;
- next if not /\A\s*extern\s+(SDL_DEPRECATED\s+|)DECLSPEC/;
- my $decl = "$_ ";
- if (not $decl =~ /\)\s*;/) {
- while (<HEADER>) {
- chomp;
- s/\A\s+//;
- s/\s+\Z//;
- $decl .= "$_ ";
- last if /\)\s*;/;
- }
- }
-
- $decl =~ s/\s+\Z//;
- #print("DECL: [$decl]\n");
-
- if ($decl =~ /\A\s*extern\s+(SDL_DEPRECATED\s+|)DECLSPEC\s+(const\s+|)(unsigned\s+|)(.*?)\s*(\*?)\s*SDLCALL\s+(.*?)\s*\((.*?)\);/) {
- my $rc = "$2$3$4$5";
- my $fn = $6;
-
- next if $existing{$fn}; # already slotted into the jump table.
-
- my @params = split(',', $7);
-
- #print("rc == '$rc', fn == '$fn', params == '$params'\n");
-
- my $retstr = ($rc eq 'void') ? '' : 'return';
- my $paramstr = '(';
- my $argstr = '(';
- my $i = 0;
- foreach (@params) {
- my $str = $_;
- $str =~ s/\A\s+//;
- $str =~ s/\s+\Z//;
- #print("1PARAM: $str\n");
- if ($str eq 'void') {
- $paramstr .= 'void';
- } elsif ($str eq '...') {
- if ($i > 0) {
- $paramstr .= ', ';
- }
- $paramstr .= $str;
- } elsif ($str =~ /\A\s*((const\s+|)(unsigned\s+|)([a-zA-Z0-9_]*)\s*([\*\s]*))\s*(.*?)\Z/) {
- #print("PARSED: [$1], [$2], [$3], [$4], [$5]\n");
- my $type = $1;
- my $var = $6;
- $type =~ s/\A\s+//;
- $type =~ s/\s+\Z//;
- $var =~ s/\A\s+//;
- $var =~ s/\s+\Z//;
- $type =~ s/\s*\*\Z/*/g;
- $type =~ s/\s*(\*+)\Z/ $1/;
- #print("SPLIT: ($type, $var)\n");
- my $var_array_suffix = "";
- # parse array suffix
- if ($var =~ /\A.*(\[.*\])\Z/) {
- #print("PARSED ARRAY SUFFIX: [$1] of '$var'\n");
- $var_array_suffix = $1;
- }
- my $name = chr(ord('a') + $i);
- if ($i > 0) {
- $paramstr .= ', ';
- $argstr .= ',';
- }
- my $spc = ($type =~ /\*\Z/) ? '' : ' ';
- $paramstr .= "$type$spc$name$var_array_suffix";
- $argstr .= "$name";
- }
- $i++;
- }
-
- $paramstr = '(void' if ($i == 0); # Just to make this consistent.
-
- $paramstr .= ')';
- $argstr .= ')';
-
- print("NEW: $decl\n");
- print SDL_DYNAPI_PROCS_H "SDL_DYNAPI_PROC($rc,$fn,$paramstr,$argstr,$retstr)\n";
- print SDL_DYNAPI_OVERRIDES_H "#define $fn ${fn}_REAL\n";
-
- $sdl_dynapi_sym_contents =~ s/# extra symbols go here/$fn;\n # extra symbols go here/;
- } else {
- print("Failed to parse decl [$decl]!\n");
- }
- }
- close(HEADER);
-}
-closedir(HEADERS);
-
-close(SDL_DYNAPI_PROCS_H);
-close(SDL_DYNAPI_OVERRIDES_H);
-
-open(SDL_DYNAPI_SYM, '>', $sdl_dynapi_sym) or die("Can't open $sdl_dynapi_sym: $!\n");
-print SDL_DYNAPI_SYM $sdl_dynapi_sym_contents;
-close(SDL_DYNAPI_SYM);
-
-# vi: set ts=4 sw=4 expandtab:
diff --git a/src/dynapi/gendynapi.py b/src/dynapi/gendynapi.py
new file mode 100755
index 000000000000..f899c4db0483
--- /dev/null
+++ b/src/dynapi/gendynapi.py
@@ -0,0 +1,449 @@
+#!/usr/bin/env python3
+
+# Simple DirectMedia Layer
+# Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+#
+# This software is provided 'as-is', without any express or implied
+# warranty. In no event will the authors be held liable for any damages
+# arising from the use of this software.
+#
+# Permission is granted to anyone to use this software for any purpose,
+# including commercial applications, and to alter it and redistribute it
+# freely, subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not
+# claim that you wrote the original software. If you use this software
+# in a product, an acknowledgment in the product documentation would be
+# appreciated but is not required.
+# 2. Altered source versions must be plainly marked as such, and must not be
+# misrepresented as being the original software.
+# 3. This notice may not be removed or altered from any source distribution.
+
+# WHAT IS THIS?
+# When you add a public API to SDL, please run this script, make sure the
+# output looks sane (git diff, it adds to existing files), and commit it.
+# It keeps the dynamic API jump table operating correctly.
+
+import re
+import os
+import argparse
+import pprint
+import json
+
+dir_path = os.path.dirname(os.path.realpath(__file__))
+
+sdl_include_dir = dir_path + "/../../include/SDL3/"
+sdl_dynapi_procs_h = dir_path + "/../../src/dynapi/SDL_dynapi_procs.h"
+sdl_dynapi_overrides_h = dir_path + "/../../src/dynapi/SDL_dynapi_overrides.h"
+sdl_dynapi_sym = dir_path + "/../../src/dynapi/SDL_dynapi.sym"
+
+full_API = []
+
+
+def main():
+
+ # Parse 'sdl_dynapi_procs_h' file to find existing functions
+ existing_procs = find_existing_procs()
+
+ # Get list of SDL headers
+ sdl_list_includes = get_header_list()
+
+ reg_externC = re.compile('.*extern[ "]*C[ "].*')
+ reg_comment_remove_content = re.compile('\/\*.*\*/')
+ reg_parsing_function = re.compile('(.*SDLCALL[^\(\)]*) ([a-zA-Z0-9_]+) *\((.*)\) *;.*')
+
+ #eg:
+ # void (SDLCALL *callback)(void*, int)
+ # \1(\2)\3
+ reg_parsing_callback = re.compile('([^\(\)]*)\(([^\(\)]+)\)(.*)')
+
+ for filename in sdl_list_includes:
+ if args.debug:
+ print("Parse header: %s" % filename)
+
+ input = open(filename)
+
+ parsing_function = False
+ current_func = ""
+ parsing_comment = False
+ current_comment = ""
+
+ for line in input:
+
+ # Discard pre-processor directives ^#.*
+ if line.startswith("#"):
+ continue
+
+ # Discard "extern C" line
+ match = reg_externC.match(line)
+ if match:
+ continue
+
+ # Remove one line comment /* ... */
+ # eg: extern DECLSPEC SDL_hid_device * SDLCALL SDL_hid_open_path(const char *path, int bExclusive /* = false */);
+ line = reg_comment_remove_content.sub('', line)
+
+ # Get the comment block /* ... */ across several lines
+ match_start = "/*" in line
+ match_end = "*/" in line
+ if match_start and match_end:
+ continue
+ if match_start:
+ parsing_comment = True
+ current_comment = line
+ continue
+ if match_end:
+ parsing_comment = False
+ current_comment += line
+ continue
+ if parsing_comment:
+ current_comment += line
+ continue
+
+ # Get the function prototype across several lines
+ if parsing_function:
+ # Append to the current function
+ current_func += " "
+ current_func += line.strip()
+ else:
+ # if is contains "extern", start grabbing
+ if "extern" not in line:
+ continue
+ # Start grabbing the new function
+ current_func = line.strip()
+ parsing_function = True;
+
+ # If it contains ';', then the function is complete
+ if ";" not in current_func:
+ continue
+
+ # Got function/comment, reset vars
+ parsing_function = False;
+ func = current_func
+ comment = current_comment
+ current_func = ""
+ current_comment = ""
+
+ # Discard if it doesn't contain 'SDLCALL'
+ if "SDLCALL" not in func:
+ if args.debug:
+ print(" Discard: " + func)
+ continue
+
+ if args.debug:
+ print(" Raw data: " + func);
+
+ # Replace unusual stuff...
+ func = func.replace("SDL_PRINTF_VARARG_FUNC(1)", "");
+ func = func.replace("SDL_PRINTF_VARARG_FUNC(2)", "");
+ func = func.replace("SDL_PRINTF_VARARG_FUNC(3)", "");
+ func = func.replace("SDL_SCANF_VARARG_FUNC(2)", "");
+ func = func.replace("__attribute__((analyzer_noreturn))", "");
+ func = func.replace("SDL_MALLOC", "");
+ func = func.replace("SDL_ALLOC_SIZE2(1, 2)", "");
+ func = func.replace("SDL_ALLOC_SIZE(2)", "");
+
+ # Should be a valid function here
+ match = reg_parsing_function.match(func)
+ if not match:
+ print("Cannot parse: "+ func)
+ exit(-1)
+
+ func_ret = match.group(1)
+ func_name = match.group(2)
+ func_params = match.group(3)
+
+ #
+ # Parse return value
+ #
+ func_ret = func_ret.replace('extern', ' ')
+ func_ret = func_ret.replace('SDLCALL', ' ')
+ func_ret = func_ret.replace('DECLSPEC', ' ')
+ # Remove trailling spaces in front of '*'
+ tmp = ""
+ while func_ret != tmp:
+ tmp = func_ret
+ func_ret = func_ret.replace(' ', ' ')
+ func_ret = func_ret.replace(' *', '*')
+ func_ret = func_ret.strip()
+
+ #
+ # Parse parameters
+ #
+ func_params = func_params.strip()
+ if func_params == "":
+ func_params = "void"
+
+ # Identify each function parameters with type and name
+ # (eventually there are callbacks of several parameters)
+ tmp = func_params.split(',')
+ tmp2 = []
+ param = ""
+ for t in tmp:
+ if param == "":
+ param = t
+ else:
+ param = param + "," + t
+ # Identify a callback or parameter when there is same count of '(' and ')'
+ if param.count('(') == param.count(')'):
+ tmp2.append(param.strip())
+ param = ""
+
+ # Process each parameters, separation name and type
+ func_param_type = []
+ func_param_name = []
+ for t in tmp2:
+ if t == "void":
+ func_param_type.append(t)
+ func_param_name.append("")
+ continue
+
+ if t == "...":
+ func_param_type.append(t)
+ func_param_name.append("")
+ continue
+
+ param_name = ""
+
+ # parameter is a callback
+ if '(' in t:
+ match = reg_parsing_callback.match(t)
+ if not match:
+ print("cannot parse callback: " + t);
+ exit(-1)
+ a = match.group(1).strip()
+ b = match.group(2).strip()
+ c = match.group(3).strip()
+
+ # cut-off last word to get callback name
+ d = b.rsplit('*', 1)[0]
+
+ # recontruct a callback name for future parsing
+ func_param_type.append(a + " (" + d + "*REWRITE_NAME)" + c)
+
+ continue
+
+ # array like "char *buf[]"
+ has_array = False
+ if t.endswith("[]"):
+ t = t.replace("[]", "")
+ has_array = True
+
+ # pointer
+ if '*' in t:
+ try:
+ (param_type, param_name) = t.rsplit('*', 1)
+ except:
+ param_type = t;
+ param_name = "param_name_not_specified"
+
+ # bug rsplit ??
+ if param_name == "":
+ param_name = "param_name_not_specified"
+
+ val = param_type.strip() + "*REWRITE_NAME"
+
+ # Remove trailling spaces in front of '*'
+ tmp = ""
+ while val != tmp:
+ tmp = val
+ val = val.replace(' ', ' ')
+ val = val.replace(' *', '*')
+ # first occurence
+ val = val.replace('*', ' *', 1)
+ val = val.strip()
+
+ else: # non pointer
+ # cut-off last word on
+ try:
+ (param_type, param_name) = t.rsplit(' ', 1)
+ except:
+ param_type = t;
+ param_name = "param_name_not_specified"
+
+ val = param_type.strip() + " REWRITE_NAME"
+
+ # set back array
+ if has_array:
+ val += "[]"
+
+ func_param_type.append(val)
+ func_param_name.append(param_name.strip())
+
+ new_proc = {}
+ # Return value type
+ new_proc['retval'] = func_ret
+ # List of parameters (type + anonymized param name 'REWRITE_NAME')
+ new_proc['parameter'] = func_param_type
+ # Real parameter name, or 'param_name_not_specified'
+ new_proc['parameter_name'] = func_param_name
+ # Function name
+ new_proc['name'] = func_name
+ # Header file
+ new_proc['header'] = os.path.basename(filename)
+ # Function comment
+ new_proc['comment'] = comment
+
+ full_API.append(new_proc)
+
+ if args.debug:
+ pprint.pprint(new_proc);
+ print("\n")
+
+ if func_name not in existing_procs:
+ print("NEW " + func)
+ add_dyn_api(new_proc)
+
+ # For-End line in input
+
+ input.close()
+ # For-End parsing all files of sdl_list_includes
+
+ # Dump API into a json file
+ full_API_json();
+
+# Dump API into a json file
+def full_API_json():
+ if args.dump:
+ filename = 'sdl.json'
+ with open(filename, 'w') as f:
+ json.dump(full_API, f, indent=4, sort_keys=True)
+ print("dump API to '%s'" % filename);
+
+# Parse 'sdl_dynapi_procs_h' file to find existing functions
+def find_existing_procs():
+ reg = re.compile('SDL_DYNAPI_PROC\([^,]*,([^,]*),.*\)')
+ ret = []
+ input = open(sdl_dynapi_procs_h)
+
+ for line in input:
+ match = reg.match(line)
+ if not match:
+ continue
+ existing_func = match.group(1)
+ ret.append(existing_func);
+ # print(existing_func)
+ input.close()
+
+ return ret
+
+# Get list of SDL headers
+def get_header_list():
+ reg = re.compile('^.*\.h$')
+ ret = []
+ tmp = os.listdir(sdl_include_dir)
+
+ for f in tmp:
+ # Only *.h files
+ match = reg.match(f)
+ if not match:
+ if args.debug:
+ print("Skip %s" % f)
+ continue
+ ret.append(sdl_include_dir + f)
+
+ return ret
+
+# Write the new API in files: _procs.h _overrivides.h and .sym
+def add_dyn_api(proc):
+ func_name = proc['name']
+ func_ret = proc['retval']
+ func_argtype = proc['parameter']
+
+
+ # File: SDL_dynapi_procs.h
+ #
+ # Add at last
+ # SDL_DYNAPI_PROC(SDL_EGLConfig,SDL_EGL_GetCurrentEGLConfig,(void),(),return)
+ f = open(sdl_dynapi_procs_h, "a")
+ dyn_proc = "SDL_DYNAPI_PROC(" + func_ret + "," + func_name + ",("
+
+ i = ord('a')
+ remove_last = False
+ for argtype in func_argtype:
+
+ # Special case, void has no parameter name
+ if argtype == "void":
+ dyn_proc += "void"
+ continue
+
+ # Var name: a, b, c, ...
+ varname = " " + chr(i)
+ i += 1
+
+ tmp = argtype.replace("REWRITE_NAME", varname)
+ dyn_proc += tmp + ","
+ remove_last = True
+
+ # remove last char ','
+ if remove_last:
+ dyn_proc = dyn_proc[:-1]
+
+ dyn_proc += "),("
+
+ i = ord('a')
+ remove_last = False
+ for argtype in func_argtype:
+
+ # Special case, void has no parameter name
+ if argtype == "void":
+ continue
+
+ # Var name: a, b, c, ...
+ varname = chr(i)
+ i += 1
+
+ dyn_proc += varname + ","
+ remove_last = True
+
+ # remove last char ','
+ if remove_last:
+ dyn_proc = dyn_proc[:-1]
+
+ dyn_proc += "),"
+
+ if func_ret != "void":
+ dyn_proc += "return"
+ dyn_proc += ")"
+ f.write(dyn_proc + "\n")
+ f.close()
+
+ # File: SDL_dynapi_overrides.h
+ #
+ # Add at last
+ # "#define SDL_DelayNS SDL_DelayNS_REAL
+ f = open(sdl_dynapi_overrides_h, "a")
+ f.write("#define " + func_name + " " + func_name + "_REAL\n")
+ f.close()
+
+ # File: SDL_dynapi.sym
+ #
+ # Add before "extra symbols go here" line
+ input = open(sdl_dynapi_sym)
+ new_input = []
+ for line in input:
+ if "extra symbols go here" in line:
+ new_input.append(" " + func_name + ";\n")
+ new_input.append(line)
+ input.close()
+ f = open(sdl_dynapi_sym, 'w')
+ for line in new_input:
+ f.write(line)
+ f.close()
+
+if __name__ == '__main__':
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--dump', help='output all SDL API into a .json file', action='store_true')
+ parser.add_argument('--debug', help='add debug traces', action='store_true')
+ args = parser.parse_args()
+
+ try:
+ main()
+ except Exception as e:
+ print(e)
+ exit(-1)
+
+ print("done!")
+ exit(0)
+