Commit fafd79b7 authored by Hitoshi Yoshida's avatar Hitoshi Yoshida Committed by Commit Bot

IDL Compiler: Implement ExtendedAttribute(s)

Implement ExtendedAttribute and ExtendedAttributes class
to follow the designed APIs.
https://docs.google.com/document/d/1ZPuPWjBSQVfVeJYptk7dYg1OAxx89-kwHz6oJlZGA5o/edit?usp=sharing

Bug: 839389
Change-Id: I21e2f4dd02433d52c14b362bd9f2cafd4eee3667
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1520518
Commit-Queue: Hitoshi Yoshida <peria@chromium.org>
Reviewed-by: default avatarKenichi Ishibashi <bashi@chromium.org>
Reviewed-by: default avatarYuki Shiino <yukishiino@chromium.org>
Cr-Commit-Position: refs/heads/master@{#642390}
parent fbfb1032
......@@ -5,20 +5,73 @@
# This file defines some classes to implement extended attributes
# https://heycam.github.io/webidl/#idl-extended-attributes
import exceptions
from itertools import groupby
class ExtendedAttribute(object):
"""ExtendedAttribute represents an exnteded attribute, except for attributes
that have their own classes.
i.e. [Constructor], [CustomConstructor], [NamedConstructor], and [Exposed].
ExtendedAttribute assumes following formats as spec defines.
"""ExtendedAttribute represents an exnteded attribute that
is described in either of following formats as spec defines.
a. [Key]
b. [Key=Value]
c. [Key=(Value1,Value2,...)]
d. [Key(Type1 Value1, Type2 Value2,...)]
e. [Key=Name(Type1 Value1, Type2 Value2,...)]
d. [Key(ValueL1 ValueR1, ValueL2 ValueR2,...)]
e. [Key=Name(ValueL1 ValueR1, ValueL2 ValueR2,...)]
"""
_FORM_NO_ARGS = 'NoArgs' # for (a)
_FORM_IDENT = 'Ident' # for (b)
_FORM_IDENT_LIST = 'IdentList' # for (c)
_FORM_ARG_LIST = 'ArgList' # for (d)
_FORM_NAMED_ARG_LIST = 'NamedArgList' # for (e)
def __init__(self, key, values=None, arguments=None, name=None):
# |values| can be either of None, a single string, or a list.
assert values is None or isinstance(values, (str, tuple, list))
# |arguments| can be either of None or a list of pairs of strings.
assert arguments is None or isinstance(arguments, (tuple, list))
self._format = None
self._key = key
self._values = values
self._arguments = arguments
self._name = name
if name is not None:
self._format = self._FORM_NAMED_ARG_LIST
if values is not None or arguments is None:
raise ValueError('Unknown format for ExtendedAttribute')
elif arguments is not None:
assert all(
isinstance(arg, (tuple, list)) and len(arg) == 2
and isinstance(arg[0], str) and isinstance(arg[1], str)
for arg in arguments)
self._format = self._FORM_ARG_LIST
if values is not None:
raise ValueError('Unknown format for ExtendedAttribute')
elif values is None:
self._format = self._FORM_NO_ARGS
elif isinstance(values, (tuple, list)):
self._format = self._FORM_IDENT_LIST
self._values = tuple(values)
elif isinstance(values, str):
self._format = self._FORM_IDENT
else:
raise ValueError('Unknown format for ExtendedAttribute')
def __str__(self):
if self._format == self._FORM_NO_ARGS:
return self._key
if self._format == self._FORM_IDENT:
return '{}={}'.format(self._key, self._values)
if self._format == self._FORM_IDENT_LIST:
return '{}=({})'.format(self._key, ', '.join(self._values))
args_str = '({})'.format(', '.join(
[' '.join(arg) for arg in self._arguments]))
if self._format == self._FORM_ARG_LIST:
return '{}{}'.format(self._key, args_str)
if self._format == self._FORM_NAMED_ARG_LIST:
return '{}={}{}'.format(self._key, self._name, args_str)
# Should not reach here.
assert False, 'Unknown format: {}'.format(self._format)
@property
def key(self):
......@@ -26,54 +79,91 @@ class ExtendedAttribute(object):
Returns the key.
@return str
"""
raise exceptions.NotImplementedError()
return self._key
@property
def value(self):
"""
Returns the value for (b). Returns None for (a). Crashes otherwise.
Returns the value for the format Ident. Returns None for the format
NoArgs. Raises an error otherwise.
@return str
"""
raise exceptions.NotImplementedError()
if self._format in (self._FORM_NO_ARGS, self._FORM_IDENT):
return self._values
raise ValueError('"{}" does not have a single value.'.format(
str(self)))
@property
def values(self):
"""
Returns a list of values for (b) and (c). Returns an empty list
for (a). Crashes otherwise.
Returns a list of values for formats Ident and IdentList. Returns an
empty list for the format NorArgs. Raises an error otherwise.
@return tuple(str)
"""
raise exceptions.NotImplementedError()
if self._format == self._FORM_NO_ARGS:
return ()
if self._format == self._FORM_IDENT:
return (self._values, )
if self._format == self._FORM_IDENT_LIST:
return self._values
raise ValueError('"{}" does not have values.'.format(str(self)))
@property
def arguments(self):
"""
Returns a tuple of arguments for (d) and (e). Crashes otherwise.
Returns a tuple of value pairs for formats ArgList and NamedArgList.
Raises an error otherwise.
@return tuple(Argument)
"""
raise exceptions.NotImplementedError()
if self._format in (self._FORM_ARG_LIST, self._FORM_NAMED_ARG_LIST):
return self._arguments
raise ValueError('"{}" does not have arguments.'.format(str(self)))
@property
def name(self):
"""
Returns |Name| for (e). Crashes otherwise.
Returns |Name| for the format NamedArgList. Raises an error otherwise.
@return str
"""
raise exceptions.NotImplementedError()
if self._format == self._FORM_NAMED_ARG_LIST:
return self._name
raise ValueError('"{}" does not have a name.'.format(str(self)))
class ExtendedAttributes(object):
"""ExtendedAttributes is a dict-like container for ExtendedAttribute instances."""
"""
ExtendedAttributes is a dict-like container for ExtendedAttribute instances.
With a key string, you can get an ExtendedAttribute or a list of them.
def has_key(self, _):
For an IDL fragment
[A, A=(foo, bar), B=baz]
the generated ExtendedAttributes instance will be like
{
'A': (ExtendedAttribute('A'), ExtendedAttribute('A', values=('foo', 'bar'))),
'B': (ExtendedAttribute('B', value='baz')),
}
"""
def __init__(self, attributes=None):
assert attributes is None or (isinstance(attributes, (list, tuple))
and all(
isinstance(attr, ExtendedAttribute)
for attr in attributes))
attributes = sorted(attributes or [], key=lambda x: x.key)
self._attributes = {
k: tuple(v)
for k, v in groupby(attributes, key=lambda x: x.key)
}
def __contains__(self, key):
"""
Returns True if this has an extended attribute with the |key|.
@param str key
@return bool
"""
raise exceptions.NotImplementedError()
return key in self._attributes
def get(self, _):
def get(self, key):
"""
Returns an exnteded attribute whose key is |key|.
If |self| has no such elements, returns None,
......@@ -81,12 +171,24 @@ class ExtendedAttributes(object):
@param str key
@return ExtendedAttriute?
"""
raise exceptions.NotImplementedError()
values = self.get_list_of(key)
if len(values) == 0:
return None
if len(values) == 1:
return values[0]
raise ValueError(
'There are multiple extended attributes for the key "{}"'.format(
key))
def get_list_of(self, _):
def get_list_of(self, key):
"""
Returns a list of extended attributes whose keys are |identifier|.
@param str key
@return tuple(ExtendedAttribute)
"""
raise exceptions.NotImplementedError()
return self._attributes.get(key, ())
def append(self, extended_attribute):
assert isinstance(extended_attribute, ExtendedAttribute)
key = extended_attribute.key
self._attributes[key] = self.get_list_of(key) + (extended_attribute, )
# Copyright 2019 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# pylint: disable=import-error,print-statement,relative-import
"""Unit tests for extended_attributes.py."""
import unittest
from .extended_attribute import ExtendedAttribute
from .extended_attribute import ExtendedAttributes
class TestableExtendedAttribute(ExtendedAttribute):
FORM_NO_ARGS = ExtendedAttribute._FORM_NO_ARGS
FORM_IDENT = ExtendedAttribute._FORM_IDENT
FORM_IDENT_LIST = ExtendedAttribute._FORM_IDENT_LIST
FORM_ARG_LIST = ExtendedAttribute._FORM_ARG_LIST
FORM_NAMED_ARG_LIST = ExtendedAttribute._FORM_NAMED_ARG_LIST
def __init__(self, key, values=None, arguments=None, name=None):
super(TestableExtendedAttribute, self).__init__(
key=key, values=values, arguments=arguments, name=name)
@property
def format(self):
return self._format
class ExtendedAttributeTest(unittest.TestCase):
def test_attribute_create(self):
# NoArgs
attr = TestableExtendedAttribute(key='Foo')
self.assertEqual(attr.format, TestableExtendedAttribute.FORM_NO_ARGS)
self.assertEqual(attr.key, 'Foo')
self.assertEqual(attr.value, None)
self.assertEqual(attr.values, ())
with self.assertRaises(ValueError):
_ = attr.arguments
with self.assertRaises(ValueError):
_ = attr.name
# Ident
attr = TestableExtendedAttribute(key='Bar', values='Val')
self.assertEqual(attr.format, TestableExtendedAttribute.FORM_IDENT)
self.assertEqual(attr.key, 'Bar')
self.assertEqual(attr.value, 'Val')
self.assertEqual(attr.values, ('Val', ))
with self.assertRaises(ValueError):
_ = attr.arguments
with self.assertRaises(ValueError):
_ = attr.name
# IdentList
attr = TestableExtendedAttribute(key='Buz', values=('Val', 'ue'))
self.assertEqual(attr.format,
TestableExtendedAttribute.FORM_IDENT_LIST)
self.assertEqual(attr.key, 'Buz')
self.assertEqual(attr.values, ('Val', 'ue'))
attr = TestableExtendedAttribute(
key='IdentList', values=['Val', 'ue', 'List'])
self.assertEqual(attr.values, ('Val', 'ue', 'List'))
with self.assertRaises(ValueError):
_ = attr.arguments
with self.assertRaises(ValueError):
_ = attr.name
# ArgList
attr = TestableExtendedAttribute(
key='Foo', arguments=(('Left', 'Right'), ('foo', 'bar')))
self.assertEqual(attr.format, TestableExtendedAttribute.FORM_ARG_LIST)
self.assertEqual(attr.key, 'Foo')
with self.assertRaises(ValueError):
_ = attr.values
self.assertEqual(attr.arguments, (('Left', 'Right'), ('foo', 'bar')))
with self.assertRaises(ValueError):
_ = attr.name
# NamedArgList
attr = TestableExtendedAttribute(
key='Bar', arguments=(('Left', 'Right'), ), name='Buz')
self.assertEqual(attr.format,
TestableExtendedAttribute.FORM_NAMED_ARG_LIST)
self.assertEqual(attr.key, 'Bar')
with self.assertRaises(ValueError):
_ = attr.values
self.assertEqual(attr.arguments, (('Left', 'Right'), ))
self.assertEqual(attr.name, 'Buz')
def test_attributes(self):
attrs = [
ExtendedAttribute(key='A', values='val'),
ExtendedAttribute(key='B'),
ExtendedAttribute(key='C', values=('Val', 'ue')),
ExtendedAttribute(key='B', values=('Val', 'ue', 'B'))
]
attributes = ExtendedAttributes(attrs)
self.assertTrue('A' in attributes)
self.assertFalse('D' in attributes)
self.assertEqual(attributes.get('A').value, 'val')
b_values = attributes.get_list_of('B')
self.assertEqual(b_values[0].values, ())
self.assertEqual(b_values[1].values, ('Val', 'ue', 'B'))
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment