WebIDL: Update webidl_binder from upstream

Use as base this files:
f36f9fcaf8/third_party/WebIDL.py
c834ef7d69/tools/tempfiles.py
f36f9fcaf8/tools/webidl_binder.py
This commit is contained in:
Thiago França da Silva 2022-02-14 22:17:44 -03:00
parent 5ebf13d6c0
commit ed8d9a5336
3 changed files with 135 additions and 102 deletions

View File

@ -1,4 +1,9 @@
# from http://mxr.mozilla.org/mozilla-central/source/dom/bindings/parser/WebIDL.py
## JavascriptSubtitlesOctopus
## Patched to:
## - add integer pointers (IntPtr)
## From https://github.com/emscripten-core/emscripten/blob/f36f9fcaf83db93e6a6d0f0cdc47ab6379ade139/third_party/WebIDL.py
# from https://hg.mozilla.org/mozilla-central/file/tip/dom/bindings/parser/WebIDL.py
# rev 501baeb3a034
# This Source Code Form is subject to the terms of the Mozilla Public
@ -4923,11 +4928,12 @@ class Parser(Tokenizer):
def __init__(self, outputdir='', lexer=None):
Tokenizer.__init__(self, outputdir, lexer)
self.parser = yacc.yacc(module=self,
self.parser = yacc.yacc(debug=0,
module=self,
outputdir=outputdir,
tabmodule='webidlyacc',
errorlog=yacc.NullLogger(),
picklefile='WebIDLGrammar.pkl')
write_tables=0,
errorlog=yacc.NullLogger())
self._globalScope = IDLScope(BuiltinLocation("<Global Scope>"), None, None)
self._installBuiltins(self._globalScope)
self._productions = []

View File

@ -1,9 +1,11 @@
## JavascriptSubtitlesOctopus
## From https://github.com/emscripten-core/emscripten/blob/c834ef7d69ccb4100239eeba0b0f6573fed063bc/tools/tempfiles.py
# Copyright 2013 The Emscripten Authors. All rights reserved.
# Emscripten is available under two separate licenses, the MIT license and the
# University of Illinois/NCSA Open Source License. Both these licenses can be
# found in the LICENSE file.
from __future__ import print_function
import os
import shutil
import tempfile
@ -28,13 +30,14 @@ def try_delete(pathname):
if not os.path.exists(pathname):
return
write_bits = stat.S_IWRITE | stat.S_IWGRP | stat.S_IWOTH
# Ensure all files are readable and writable by the current user.
permission_bits = stat.S_IWRITE | stat.S_IREAD
def is_writable(path):
return (os.stat(path).st_mode & write_bits) == write_bits
return (os.stat(path).st_mode & permission_bits) != permission_bits
def make_writable(path):
os.chmod(path, os.stat(path).st_mode | write_bits)
os.chmod(path, os.stat(path).st_mode | permission_bits)
# Some tests make files and subdirectories read-only, so rmtree/unlink will not delete
# them. Force-make everything writable in the subdirectory to make it
@ -54,9 +57,9 @@ def try_delete(pathname):
pass
class TempFiles(object):
def __init__(self, tmp, save_debug_files=False):
self.tmp = tmp
class TempFiles:
def __init__(self, tmpdir, save_debug_files):
self.tmpdir = tmpdir
self.save_debug_files = save_debug_files
self.to_clean = []
@ -67,17 +70,19 @@ class TempFiles(object):
def get(self, suffix):
"""Returns a named temp file with the given prefix."""
named_file = tempfile.NamedTemporaryFile(dir=self.tmp, suffix=suffix, delete=False)
named_file = tempfile.NamedTemporaryFile(dir=self.tmpdir, suffix=suffix, delete=False)
self.note(named_file.name)
return named_file
def get_file(self, suffix):
"""Returns an object representing a RAII-like access to a temp file, that has convenient pythonesque
semantics for being used via a construct 'with TempFiles.get_file(..) as filename:'. The file will be
deleted immediately once the 'with' block is exited."""
class TempFileObject(object):
"""Returns an object representing a RAII-like access to a temp file
that has convenient pythonesque semantics for being used via a construct
'with TempFiles.get_file(..) as filename:'.
The file will be deleted immediately once the 'with' block is exited.
"""
class TempFileObject:
def __enter__(self_):
self_.file = tempfile.NamedTemporaryFile(dir=self.tmp, suffix=suffix, delete=False)
self_.file = tempfile.NamedTemporaryFile(dir=self.tmpdir, suffix=suffix, delete=False)
self_.file.close() # NamedTemporaryFile passes out open file handles, but callers prefer filenames (and open their own handles manually if needed)
return self_.file.name
@ -88,20 +93,14 @@ class TempFiles(object):
def get_dir(self):
"""Returns a named temp directory with the given prefix."""
directory = tempfile.mkdtemp(dir=self.tmp)
directory = tempfile.mkdtemp(dir=self.tmpdir)
self.note(directory)
return directory
def clean(self):
if self.save_debug_files:
print('not cleaning up temp files since in debug-save mode, see them in %s' % (self.tmp,), file=sys.stderr)
print(f'not cleaning up temp files since in debug-save mode, see them in {self.tmpdir}', file=sys.stderr)
return
for filename in self.to_clean:
try_delete(filename)
self.to_clean = []
def run_and_clean(self, func):
try:
return func()
finally:
self.clean()

View File

@ -1,28 +1,26 @@
## JavascriptSubtitlesOctopus
## Patched to:
## - add integer pointers (IntPtr)
## - implement ByteString type
## From https://github.com/emscripten-core/emscripten/blob/f36f9fcaf83db93e6a6d0f0cdc47ab6379ade139/tools/webidl_binder.py
# Copyright 2014 The Emscripten Authors. All rights reserved.
# Emscripten is available under two separate licenses, the MIT license and the
# University of Illinois/NCSA Open Source License. Both these licenses can be
# found in the LICENSE file.
# This is a patched WebIDL to work with libass
"""WebIDL binder
'''
WebIDL binder
http://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.html
'''
from __future__ import print_function
import os, sys
sys.path.insert(1, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
https://emscripten.org/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.html
"""
import os
import sys
from tempfiles import try_delete
print(os.path.join('/', 'emsdk', 'upstream', 'emscripten'))
def path_from_root(*pathelems):
return os.path.join(os.path.join('/', 'emsdk', 'upstream', 'emscripten'), *pathelems)
sys.path.append(path_from_root('third_party', 'ply'))
import WebIDL
@ -32,23 +30,26 @@ import WebIDL
# CHECKS='ALL' will do extensive argument type checking (~5x slower than default).
# This will catch invalid numbers, invalid pointers, invalid strings, etc.
# Anything else defaults to legacy mode for backward compatibility.
CHECKS = os.environ.get('IDL_CHECKS') or 'DEFAULT'
CHECKS = os.environ.get('IDL_CHECKS', 'DEFAULT')
# DEBUG=1 will print debug info in render_function
DEBUG = os.environ.get('IDL_VERBOSE') == '1'
if DEBUG: print("Debug print ON, CHECKS=%s" % CHECKS)
if DEBUG:
print("Debug print ON, CHECKS=%s" % CHECKS)
# We need to avoid some closure errors on the constructors we define here.
CONSTRUCTOR_CLOSURE_SUPPRESSIONS = '/** @suppress {undefinedVars, duplicate} */'
CONSTRUCTOR_CLOSURE_SUPPRESSIONS = '/** @suppress {undefinedVars, duplicate} @this{Object} */'
class Dummy(object):
class Dummy:
def __init__(self, init):
for k, v in init.items():
self.__dict__[k] = v
def getExtendedAttribute(self, name):
def getExtendedAttribute(self, name): # noqa: U100
return None
input_file = sys.argv[1]
output_base = sys.argv[2]
@ -74,8 +75,8 @@ for thing in data:
elif isinstance(thing, WebIDL.IDLEnum):
enums[thing.identifier.name] = thing
#print interfaces
#print implements
# print interfaces
# print implements
pre_c = []
mid_c = []
@ -89,6 +90,7 @@ mid_c += [r'''
extern "C" {
''']
def build_constructor(name):
implementing_name = implements[name][0] if implements.get(name) else 'WrapperObject'
return [r'''{name}.prototype = Object.create({implementing}.prototype);
@ -110,13 +112,15 @@ function WrapperObject() {
mid_js += build_constructor('WrapperObject')
mid_js += ['''
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant) */
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant)
@param {*=} __class__ */
function getCache(__class__) {
return (__class__ || WrapperObject).__cache__;
}
Module['getCache'] = getCache;
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant) */
/** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant)
@param {*=} __class__ */
function wrapPointer(ptr, __class__) {
var cache = getCache(__class__);
var ret = cache[ptr];
@ -213,15 +217,15 @@ var ensureCache = {
return ret;
},
copy: function(array, view, offset) {
var offsetShifted = offset;
offset >>>= 0;
var bytes = view.BYTES_PER_ELEMENT;
switch (bytes) {
case 2: offsetShifted >>= 1; break;
case 4: offsetShifted >>= 2; break;
case 8: offsetShifted >>= 3; break;
case 2: offset >>>= 1; break;
case 4: offset >>>= 2; break;
case 8: offset >>>= 3; break;
}
for (var i = 0; i < array.length; i++) {
view[offsetShifted + i] = array[i];
view[offset + i] = array[i];
}
},
};
@ -286,22 +290,27 @@ function ensureFloat64(value) {
mid_c += ['''
// Not using size_t for array indices as the values used by the javascript code are signed.
EM_JS(void, array_bounds_check_error, (size_t idx, size_t size), {
throw 'Array index ' + idx + ' out of bounds: [0,' + size + ')';
});
void array_bounds_check(const int array_size, const int array_idx) {
if (array_idx < 0 || array_idx >= array_size) {
EM_ASM({
throw 'Array index ' + $0 + ' out of bounds: [0,' + $1 + ')';
}, array_idx, array_size);
array_bounds_check_error(array_idx, array_size);
}
}
''']
C_FLOATS = ['float', 'double']
def full_typename(arg):
return ('const ' if arg.getExtendedAttribute('Const') else '') + arg.type.name + ('[]' if arg.type.isArray() else '')
def type_to_c(t, non_pointing=False):
#print 'to c ', t
# print 'to c ', t
def base_type_to_c(t):
if t == 'Long':
ret = 'int'
@ -353,31 +362,37 @@ def type_to_c(t, non_pointing=False):
prefix = 'const '
return prefix + base_type_to_c(t) + suffix
def take_addr_if_nonpointer(m):
if m.getExtendedAttribute('Ref') or m.getExtendedAttribute('Value'):
return '&'
return ''
def deref_if_nonpointer(m):
if m.getExtendedAttribute('Ref') or m.getExtendedAttribute('Value'):
return '*'
return ''
def type_to_cdec(raw):
name = ret = type_to_c(raw.type.name, non_pointing=True)
if raw.getExtendedAttribute('Const'): ret = 'const ' + ret
if raw.type.name not in interfaces: return ret
ret = type_to_c(raw.type.name, non_pointing=True)
if raw.getExtendedAttribute('Const'):
ret = 'const ' + ret
if raw.type.name not in interfaces:
return ret
if raw.getExtendedAttribute('Ref'):
return ret + '&'
if raw.getExtendedAttribute('Value'):
return ret
return ret + '*'
def render_function(class_name, func_name, sigs, return_type, non_pointer, copy, operator, constructor, func_scope, call_content=None, const=False, array_attribute=False):
global mid_c, mid_js, js_impl_methods
def render_function(class_name, func_name, sigs, return_type, non_pointer,
copy, operator, constructor, func_scope,
call_content=None, const=False, array_attribute=False):
legacy_mode = CHECKS not in ['ALL', 'FAST']
all_checks = CHECKS == 'ALL'
all_checks = CHECKS == 'ALL'
bindings_name = class_name + '_' + func_name
min_args = min(sigs.keys())
@ -399,7 +414,8 @@ def render_function(class_name, func_name, sigs, return_type, non_pointer, copy,
cache = ('getCache(%s)[this.ptr] = this;' % class_name) if constructor else ''
call_prefix = '' if not constructor else 'this.ptr = '
call_postfix = ''
if return_type != 'Void' and not constructor: call_prefix = 'return '
if return_type != 'Void' and not constructor:
call_prefix = 'return '
if not constructor:
if return_type in interfaces:
call_prefix += 'wrapPointer('
@ -449,23 +465,29 @@ def render_function(class_name, func_name, sigs, return_type, non_pointer, copy,
body += " /* %s <%s> [%s] */\n" % (js_arg, arg.type.name, inner)
# Wrap asserts with existence check when argument is optional.
if all_checks and optional: body += "if(typeof {0} !== 'undefined' && {0} !== null) {{\n".format(js_arg)
if all_checks and optional:
body += "if(typeof {0} !== 'undefined' && {0} !== null) {{\n".format(js_arg)
# Special case argument types.
if arg.type.isNumeric():
if arg.type.isInteger():
if all_checks: body += " assert(typeof {0} === 'number' && !isNaN({0}), '{1}Expecting <integer>');\n".format(js_arg, check_msg)
if all_checks:
body += " assert(typeof {0} === 'number' && !isNaN({0}), '{1}Expecting <integer>');\n".format(js_arg, check_msg)
else:
if all_checks: body += " assert(typeof {0} === 'number', '{1}Expecting <number>');\n".format(js_arg, check_msg)
if all_checks:
body += " assert(typeof {0} === 'number', '{1}Expecting <number>');\n".format(js_arg, check_msg)
# No transform needed for numbers
elif arg.type.isBoolean():
if all_checks: body += " assert(typeof {0} === 'boolean' || (typeof {0} === 'number' && !isNaN({0})), '{1}Expecting <boolean>');\n".format(js_arg, check_msg)
if all_checks:
body += " assert(typeof {0} === 'boolean' || (typeof {0} === 'number' && !isNaN({0})), '{1}Expecting <boolean>');\n".format(js_arg, check_msg)
# No transform needed for booleans
elif arg.type.isString():
# Strings can be DOM strings or pointers.
if all_checks: body += " assert(typeof {0} === 'string' || ({0} && typeof {0} === 'object' && typeof {0}.ptr === 'number'), '{1}Expecting <string>');\n".format(js_arg, check_msg)
if all_checks:
body += " assert(typeof {0} === 'string' || ({0} && typeof {0} === 'object' && typeof {0}.ptr === 'number'), '{1}Expecting <string>');\n".format(js_arg, check_msg)
do_default = True # legacy path is fast enough for strings.
elif arg.type.isInterface():
if all_checks: body += " assert(typeof {0} === 'object' && typeof {0}.ptr === 'number', '{1}Expecting <pointer>');\n".format(js_arg, check_msg)
if all_checks:
body += " assert(typeof {0} === 'object' && typeof {0}.ptr === 'number', '{1}Expecting <pointer>');\n".format(js_arg, check_msg)
if optional:
body += " if(typeof {0} !== 'undefined' && {0} !== null) {{ {0} = {0}.ptr }};\n".format(js_arg)
else:
@ -474,7 +496,8 @@ def render_function(class_name, func_name, sigs, return_type, non_pointer, copy,
else:
do_default = True
if all_checks and optional: body += "}\n"
if all_checks and optional:
body += "}\n"
else:
do_default = True
@ -505,15 +528,16 @@ def render_function(class_name, func_name, sigs, return_type, non_pointer, copy,
body += ' %s%s(%s)%s;\n' % (call_prefix, '_' + c_names[max_args], ', '.join(pre_arg + args), call_postfix)
if cache:
body += ' ' + cache + '\n'
mid_js += [r'''%sfunction%s(%s) {
mid_js.append(r'''%sfunction%s(%s) {
%s
};''' % (CONSTRUCTOR_CLOSURE_SUPPRESSIONS, (' ' + func_name) if constructor else '', ', '.join(args), body[:-1])]
};''' % (CONSTRUCTOR_CLOSURE_SUPPRESSIONS, (' ' + func_name) if constructor else '', ', '.join(args), body[:-1]))
# C
for i in range(min_args, max_args+1):
for i in range(min_args, max_args + 1):
raw = sigs.get(i)
if raw is None: continue
if raw is None:
continue
sig = list(map(full_typename, raw))
if array_attribute:
sig = [x.replace('[]', '') for x in sig] # for arrays, ignore that this is an array - our get/set methods operate on the elements
@ -554,7 +578,7 @@ def render_function(class_name, func_name, sigs, return_type, non_pointer, copy,
return_prefix = basic_return
return_postfix = ''
if non_pointer:
return_prefix += '&';
return_prefix += '&'
if copy:
pre += ' static %s temp;\n' % type_to_c(return_type, non_pointing=True)
return_prefix += '(temp = '
@ -562,23 +586,23 @@ def render_function(class_name, func_name, sigs, return_type, non_pointer, copy,
c_return_type = type_to_c(return_type)
maybe_const = 'const ' if const else ''
mid_c += [r'''
mid_c.append(r'''
%s%s EMSCRIPTEN_KEEPALIVE %s(%s) {
%s %s%s%s;
}
''' % (maybe_const, type_to_c(class_name) if constructor else c_return_type, c_names[i], full_args, pre, return_prefix, call, return_postfix)]
''' % (maybe_const, type_to_c(class_name) if constructor else c_return_type, c_names[i], full_args, pre, return_prefix, call, return_postfix))
if not constructor:
if i == max_args:
dec_args = ', '.join([type_to_cdec(raw[j]) + ' ' + args[j] for j in range(i)])
js_call_args = ', '.join(['%s%s' % (('(int)' if sig[j] in interfaces else '') + take_addr_if_nonpointer(raw[j]), args[j]) for j in range(i)])
js_call_args = ', '.join(['%s%s' % (('(ptrdiff_t)' if sig[j] in interfaces else '') + take_addr_if_nonpointer(raw[j]), args[j]) for j in range(i)])
js_impl_methods += [r''' %s %s(%s) %s {
js_impl_methods.append(r''' %s %s(%s) %s {
%sEM_ASM_%s({
var self = Module['getCache'](Module['%s'])[$0];
if (!self.hasOwnProperty('%s')) throw 'a JSImplementation must implement all functions, you forgot %s::%s.';
%sself['%s'](%s)%s;
}, (int)this%s);
}, (ptrdiff_t)this%s);
}''' % (c_return_type, func_name, dec_args, maybe_const,
basic_return, 'INT' if c_return_type not in C_FLOATS else 'DOUBLE',
class_name,
@ -587,12 +611,13 @@ def render_function(class_name, func_name, sigs, return_type, non_pointer, copy,
func_name,
','.join(['$%d' % i for i in range(1, max_args + 1)]),
return_postfix,
(', ' if js_call_args else '') + js_call_args)]
(', ' if js_call_args else '') + js_call_args))
for name, interface in interfaces.items():
js_impl = interface.getExtendedAttribute('JSImplementation')
if not js_impl: continue
if not js_impl:
continue
implements[name] = [js_impl[0]]
# Compute the height in the inheritance tree of each node. Note that the order of interation
@ -623,11 +648,11 @@ for name in names:
mid_js += ['\n// ' + name + '\n']
mid_c += ['\n// ' + name + '\n']
global js_impl_methods
js_impl_methods = []
cons = interface.getExtendedAttribute('Constructor')
if type(cons) == list: raise Exception('do not use "Constructor", instead create methods with the name of the interface')
if type(cons) == list:
raise Exception('do not use "Constructor", instead create methods with the name of the interface')
js_impl = interface.getExtendedAttribute('JSImplementation')
if js_impl:
@ -641,7 +666,8 @@ for name in names:
mid_js += build_constructor(name)
for m in interface.members:
if not m.isMethod(): continue
if not m.isMethod():
continue
constructor = m.identifier.name == name
if not constructor:
parent_constructor = False
@ -662,7 +688,7 @@ for name in names:
return_type = ret.name
else:
assert return_type == ret.name, 'overloads must have the same return type'
for i in range(len(args)+1):
for i in range(len(args) + 1):
if i == len(args) or args[i].optional:
assert i not in sigs, 'overloading must differentiate by # of arguments (cannot have two signatures that differ by types but not by length)'
sigs[i] = args[:i]
@ -679,13 +705,14 @@ for name in names:
mid_js += build_constructor(name)
for m in interface.members:
if not m.isAttr(): continue
if not m.isAttr():
continue
attr = m.identifier.name
if m.type.isArray():
get_sigs = { 1: [Dummy({ 'type': WebIDL.BuiltinTypes[WebIDL.IDLBuiltinType.Types.long] })] }
set_sigs = { 2: [Dummy({ 'type': WebIDL.BuiltinTypes[WebIDL.IDLBuiltinType.Types.long] }),
Dummy({ 'type': m.type })] }
get_sigs = {1: [Dummy({'type': WebIDL.BuiltinTypes[WebIDL.IDLBuiltinType.Types.long]})]}
set_sigs = {2: [Dummy({'type': WebIDL.BuiltinTypes[WebIDL.IDLBuiltinType.Types.long]}),
Dummy({'type': m.type})]}
get_call_content = take_addr_if_nonpointer(m) + 'self->' + attr + '[arg0]'
set_call_content = 'self->' + attr + '[arg0] = ' + deref_if_nonpointer(m) + 'arg1'
if m.getExtendedAttribute('BoundsChecked'):
@ -693,8 +720,8 @@ for name in names:
get_call_content = "(%s, %s)" % (bounds_check, get_call_content)
set_call_content = "(%s, %s)" % (bounds_check, set_call_content)
else:
get_sigs = { 0: [] }
set_sigs = { 1: [Dummy({ 'type': m.type })] }
get_sigs = {0: []}
set_sigs = {1: [Dummy({'type': m.type})]}
get_call_content = take_addr_if_nonpointer(m) + 'self->' + attr
set_call_content = 'self->' + attr + ' = ' + deref_if_nonpointer(m) + 'arg0'
@ -732,12 +759,11 @@ for name in names:
mid_js += [r'''
Object.defineProperty(%s.prototype, '%s', { get: %s.prototype.%s, set: %s.prototype.%s });''' % (name, attr, name, get_name, name, set_name)]
if not interface.getExtendedAttribute('NoDelete'):
mid_js += [r'''
%s.prototype['__destroy__'] = %s.prototype.__destroy__ = ''' % (name, name)]
render_function(name,
'__destroy__', { 0: [] }, 'Void',
'__destroy__', {0: []}, 'Void',
None,
None,
None,
@ -775,8 +801,7 @@ for name, enum in enums.items():
[namespace, identifier] = symbols
if namespace in interfaces:
# namespace is a class
deferred_js += ["Module['%s']['%s'] = _%s();\n" % \
(namespace, identifier, function_id)]
deferred_js += ["Module['%s']['%s'] = _%s();\n" % (namespace, identifier, function_id)]
else:
# namespace is a namespace, so the enums get collapsed into the top level namespace.
deferred_js += ["Module['%s'] = _%s();\n" % (identifier, function_id)]
@ -784,22 +809,25 @@ for name, enum in enums.items():
raise Exception("Illegal enum value %s" % value)
mid_c += ['\n}\n\n']
mid_js += ['''
if len(deferred_js):
mid_js += ['''
(function() {
function setupEnums() {
%s
}
if (runtimeInitialized) setupEnums();
else addOnPreMain(setupEnums);
else addOnInit(setupEnums);
})();
''' % '\n '.join(deferred_js)]
# Write
with open(output_base + '.cpp', 'w') as c:
for x in pre_c: c.write(x)
for x in mid_c: c.write(x)
for x in pre_c:
c.write(x)
for x in mid_c:
c.write(x)
with open(output_base + '.js', 'w') as js:
for x in mid_js: js.write(x)
for x in mid_js:
js.write(x)