Skip to content
Open
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ Return a named construct. If a single name is provided, a breadth-first search t

Return a list of named constructs. Accepts the same search paths as Parser.find(name).

**Parser.find_method(name)**

Returns a specific method. Uses the path syntax of `Parser.find()`, and also takes arguments into account if possible (normalizing as per `Parser.normalized_method_name()`), to distinguish between overloads. If multiple methods match the passed string, returns the last such one.

**Parser.find_methods(name)**

Returns all matching methods.

**Parser.normalized_method_name(name [, interface_name=None])**

Provide a normalized version of a method name, including the names of all arguments, e.g. "drawCircle(long x, long y, long radius)" becomes: "drawCircle(x, y, radius)". If a valid set of arguments is passed, the passed argument names will be returned in the normalized form. Otherwise, a search is performed for a matching previously parsed method name. The search may be narrowed to a particular interface by passing the name fo the interface or callbak in interface_name.
Expand Down
130 changes: 120 additions & 10 deletions widlparser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,9 @@ def find(self, name: str) -> (Construct | None):

Searches entire tree in reverse order.
"""
match = re.match(r'(.*)\(.*\)(.*)', name) # strip ()'s
while (match):
name = match.group(1) + match.group(2)
match = re.match(r'(.*)\(.*\)(.*)', name)
# Specialize on methods
if '(' in name:
return self.find_method(name)

path = None
if ('/' in name):
Expand Down Expand Up @@ -214,12 +213,11 @@ def find_all(self, name: str) -> list[Construct]:
"""
Find all constructs with a given name.

Searches entire tree.
Searches entire tree in reverse order.
"""
match = re.match(r'(.*)\(.*\)(.*)', name) # strip ()'s
while (match):
name = match.group(1) + match.group(2)
match = re.match(r'(.*)\(.*\)(.*)', name)
# Specialize on methods
if '(' in name:
return self.find_methods(name)

path = None
if ('/' in name):
Expand Down Expand Up @@ -251,6 +249,7 @@ def find_all(self, name: str) -> list[Construct]:
argument = construct.find_argument(argument_name, False)
if (argument):
result.append(argument)
result.reverse()
return result

for construct in self.constructs:
Expand All @@ -265,10 +264,109 @@ def find_all(self, name: str) -> list[Construct]:
for construct in self.constructs:
result += construct.find_arguments(name)

result.reverse()
return result

def find_method(self, method_text: str) -> Construct | None:
"""Find a method with a given name, and matching args if passed."""
match = re.match(r'(?:([^./(]+)[./])?([^./(]+)(?:\((.*)\))?', method_text)
if (not match):
return None
interface_name, name, arg_text = match.groups()

empty_args = False # Empty args might indicate explicitly zero arguments, or just args not passed
if (arg_text is not None):
tokens = Tokenizer(arg_text)
if (productions.ArgumentList.peek(tokens)):
arguments = productions.ArgumentList(tokens, None)
arg_text = arguments.argument_names[0]
argument_names = [argument.strip() for argument in arg_text.split(',') if argument.strip() != '']
if len(argument_names) == 0:
empty_args = True
else:
argument_names = None

if (interface_name):
interface = self.find(interface_name)
if (interface):
method = interface.find_method(name, argument_names)
if (not method and empty_args):
method = interface.find_method(name)
return method
return None

construct: (Construct | None)
for construct in reversed(self.constructs):
method = construct.find_method(name, argument_names)
if (not method and empty_args):
method = construct.find_method(name)
if (method):
return method

construct = self.find(name)
if (construct and ('method' == construct.idl_type)):
return construct

return None

def find_methods(self, method_text: str) -> list[Construct]:
"""Find all methods with a given name, and matching args if passed."""
match = re.match(r'(?:([^./(]+)[./])?([^./(]+)(?:\((.*)\))?', method_text)
if (not match):
return []
interface_name, name, arg_text = match.groups()

empty_args = False # Empty args might indicate explicitly zero arguments, or just args not passed
if (arg_text is not None):
tokens = Tokenizer(arg_text)
if (productions.ArgumentList.peek(tokens)):
arguments = productions.ArgumentList(tokens, None)
arg_text = arguments.argument_names[0]
argument_names = [argument.strip() for argument in arg_text.split(',') if argument.strip() != '']
if len(argument_names) == 0:
empty_args = True
else:
argument_names = None

if (interface_name):
interface = self.find(interface_name)
if (interface):
methods = interface.find_methods(name, argument_names)
if (not methods and empty_args):
methods = interface.find_methods(name)
methods.reverse()
return methods
return []

result = []
construct: (Construct | None)
for construct in self.constructs:
methods = construct.find_methods(name, argument_names)
if (not methods and empty_args):
methods = construct.find_methods(name)
if (methods):
result.extend(methods)
if (result):
result.reverse()
return result

construct = self.find(name)
if (construct and ('method' == construct.idl_type)):
return [construct]

return []

def normalized_method_name(self, method_text: str, interface_name: (str | None) = None) -> str:
"""Return normalized name for a method description."""
"""
Return normalized name for a method description.

If passed a full WebIDL signature, normalize purely based on the parsed text.
Otherwise, try to find the construct in the parser and use that to normalize;
if that fails, then do a naive normalization based on the text.
The interface for a method is optional,
can be specified in the method_text as a path (like `find()`)
or as a separate argument.
"""
argument_names: (list[str] | None)
match = re.match(r'(.*)\((.*)\)(.*)', method_text)
if (match):
Expand All @@ -282,6 +380,12 @@ def normalized_method_name(self, method_text: str, interface_name: (str | None)
name = method_text
argument_names = None

if (not interface_name):
if '/' in name:
interface_name, name = name.split('/', 1)
elif '.' in name:
interface_name, name = name.split('.', 1)

if (interface_name):
interface = self.find(interface_name)
if (interface):
Expand Down Expand Up @@ -316,6 +420,12 @@ def normalized_method_names(self, method_text: str, interface_name: (str | None)
name = method_text
argument_names = None

if (not interface_name):
if '/' in name:
interface_name, name = name.split('/', 1)
elif '.' in name:
interface_name, name = name.split('.', 1)

if (interface_name):
interface = self.find(interface_name)
if (interface):
Expand Down