summaryrefslogtreecommitdiff
path: root/src/select
diff options
context:
space:
mode:
authorLucas Neves <lcneves@gmail.com>2017-11-13 21:13:06 +0000
committerLucas Neves <lcneves@gmail.com>2017-11-13 21:13:06 +0000
commit45fb75ae50ab66ba4098beec956ee0695916589c (patch)
tree48b19af9f7938dc93a9d80b858d646f9c89f2f34 /src/select
parenta827023bed3815e332a33dc9f061584cbf424313 (diff)
downloadlibcss-45fb75ae50ab66ba4098beec956ee0695916589c.tar.gz
libcss-45fb75ae50ab66ba4098beec956ee0695916589c.tar.bz2
Select: autogenerator for computed.h, propset.h and propget.h.
Diffstat (limited to 'src/select')
-rw-r--r--src/select/.gitignore5
-rw-r--r--src/select/Makefile4
-rw-r--r--src/select/assets.py30
-rw-r--r--src/select/overrides.py226
-rw-r--r--src/select/select_config.py176
-rw-r--r--src/select/select_generator.py832
6 files changed, 1273 insertions, 0 deletions
diff --git a/src/select/.gitignore b/src/select/.gitignore
new file mode 100644
index 0000000..a8bedc3
--- /dev/null
+++ b/src/select/.gitignore
@@ -0,0 +1,5 @@
+autogenerated_*.c
+autogenerated_*.h
+__pycache__
+*.pyc
+
diff --git a/src/select/Makefile b/src/select/Makefile
index e937191..13c3eec 100644
--- a/src/select/Makefile
+++ b/src/select/Makefile
@@ -1,4 +1,8 @@
# Sources
+select_generator:
+ python3 src/select/select_generator.py
+
DIR_SOURCES := arena.c computed.c dispatch.c hash.c select.c font_face.c
+PRE_TARGETS := select_generator
include $(NSBUILD)/Makefile.subdir
diff --git a/src/select/assets.py b/src/select/assets.py
new file mode 100644
index 0000000..67c6b6b
--- /dev/null
+++ b/src/select/assets.py
@@ -0,0 +1,30 @@
+# This file is part of LibCSS.
+# Licensed under the MIT License,
+# http://www.opensource.org/licenses/mit-license.php
+# Copyright 2017 Lucas Neves <lcneves@gmail.com>
+
+copyright = '''\
+/*
+ * This file is part of LibCSS
+ * Licensed under the MIT License,
+ * http://www.opensource.org/licenses/mit-license.php
+ * Copyright 2017 The NetSurf Project
+ */
+'''
+
+assets = {}
+
+assets['computed.h'] = {}
+assets['computed.h']['header'] = copyright
+assets['computed.h']['footer'] = ''
+
+assets['propset.h'] = {}
+assets['propset.h']['header'] = copyright + '''
+/** Default values are 'initial value', unless the property is inherited,
+ * in which case it is 'inherit'. */'''
+assets['propset.h']['footer'] = ''
+
+assets['propget.h'] = {}
+assets['propget.h']['header'] = copyright
+assets['propget.h']['footer'] = ''
+
diff --git a/src/select/overrides.py b/src/select/overrides.py
new file mode 100644
index 0000000..fc2117b
--- /dev/null
+++ b/src/select/overrides.py
@@ -0,0 +1,226 @@
+# This file is part of LibCSS.
+# Licensed under the MIT License,
+# http://www.opensource.org/licenses/mit-license.php
+# Copyright 2017 Lucas Neves <lcneves@gmail.com>
+
+overrides = {
+ 'get': {},
+ 'set': {},
+ 'properties': {}
+}
+
+overrides['get']['clip'] = '''\
+static inline uint8_t get_clip(
+ const css_computed_style *style,
+ css_computed_clip_rect *rect)
+{
+ if (style->i.uncommon != NULL) {
+ uint32_t bits = style->i.uncommon->i.bits[CLIP_INDEX];
+ bits &= CLIP_MASK;
+ bits >>= CLIP_SHIFT;
+
+ /*
+ 26bits: tt tttr rrrr bbbb blll llTR BLyy:
+ units: top | right | bottom | left
+ opcodes: top | right | bottom | left | type
+ */
+
+ if ((bits & 0x3) == CSS_CLIP_RECT) {
+ rect->left_auto = (bits & 0x4);
+ rect->bottom_auto = (bits & 0x8);
+ rect->right_auto = (bits & 0x10);
+ rect->top_auto = (bits & 0x20);
+
+ rect->top = style->i.uncommon->i.clip_a;
+ rect->tunit = bits & 0x3e00000;
+
+ rect->right = style->i.uncommon->i.clip_b;
+ rect->runit = bits & 0x1f0000;
+
+ rect->bottom = style->i.uncommon->i.clip_c;
+ rect->bunit = bits & 0xf800;
+
+ rect->left = style->i.uncommon->i.clip_d;
+ rect->lunit = bits & 0x7c0;
+ }
+
+ return (bits & 0x3);
+ }
+
+ /* Initial value */
+ return CSS_CLIP_AUTO;
+}'''
+
+overrides['set']['clip'] = '''\
+static inline css_error set_clip(
+ css_computed_style *style, uint8_t type,
+ css_computed_clip_rect *rect)
+{
+ uint32_t *bits;
+
+ ENSURE_UNCOMMON;
+
+ bits = &style->i.uncommon->i.bits[CLIP_INDEX];
+
+ /*
+ 26bits: tt tttr rrrr bbbb blll llTR BLyy:
+ units: top | right | bottom | left
+ opcodes: top | right | bottom | left | type
+ */
+ *bits = (*bits & ~CLIP_MASK) |
+ ((type & 0x3) << CLIP_SHIFT);
+
+ if (type == CSS_CLIP_RECT) {
+ *bits |= (((rect->top_auto ? 0x20 : 0) |
+ (rect->right_auto ? 0x10 : 0) |
+ (rect->bottom_auto ? 0x8 : 0) |
+ (rect->left_auto ? 0x4 : 0)) << CLIP_SHIFT);
+
+ *bits |= (((rect->tunit << 5) | rect->runit)
+ << (CLIP_SHIFT + 16));
+
+ *bits |= (((rect->bunit << 5) | rect->lunit)
+ << (CLIP_SHIFT + 6));
+
+ style->i.uncommon->i.clip_a = rect->top;
+ style->i.uncommon->i.clip_b = rect->right;
+ style->i.uncommon->i.clip_c = rect->bottom;
+ style->i.uncommon->i.clip_d = rect->left;
+ }
+
+ return CSS_OK;
+}'''
+
+overrides['get']['line_height'] = '''\
+static inline uint8_t get_line_height(
+ const css_computed_style *style,
+ css_fixed *length, css_unit *unit)
+{
+ uint32_t bits = style->i.bits[LINE_HEIGHT_INDEX];
+ bits &= LINE_HEIGHT_MASK;
+ bits >>= LINE_HEIGHT_SHIFT;
+
+ /* 7bits: uuuuutt : units | type */
+ if ((bits & 0x3) == CSS_LINE_HEIGHT_NUMBER ||
+ (bits & 0x3) == CSS_LINE_HEIGHT_DIMENSION) {
+ *length = style->i.line_height;
+ }
+
+ if ((bits & 0x3) == CSS_LINE_HEIGHT_DIMENSION) {
+ *unit = bits >> 2;
+ }
+
+ return (bits & 0x3);
+}'''
+
+overrides['set']['content'] = '''\
+static inline css_error set_content(
+ css_computed_style *style, uint8_t type,
+ css_computed_content_item *content)
+{
+ uint32_t *bits;
+ css_computed_content_item *oldcontent;
+ css_computed_content_item *c;
+
+ ENSURE_UNCOMMON;
+
+ /* 2bits: type */
+ bits = &style->i.uncommon->i.bits[CONTENT_INDEX];
+ oldcontent = style->i.uncommon->content;
+
+ *bits = (*bits & ~CONTENT_MASK) |
+ ((type & 0x3) << CONTENT_SHIFT);
+
+ for (c = content; c != NULL &&
+ c->type != CSS_COMPUTED_CONTENT_NONE; c++) {
+ switch (c->type) {
+ case CSS_COMPUTED_CONTENT_STRING:
+ c->data.string = lwc_string_ref(c->data.string);
+ break;
+ case CSS_COMPUTED_CONTENT_URI:
+ c->data.uri = lwc_string_ref(c->data.uri);
+ break;
+ case CSS_COMPUTED_CONTENT_ATTR:
+ c->data.attr = lwc_string_ref(c->data.attr);
+ break;
+ case CSS_COMPUTED_CONTENT_COUNTER:
+ c->data.counter.name =
+ lwc_string_ref(c->data.counter.name);
+ break;
+ case CSS_COMPUTED_CONTENT_COUNTERS:
+ c->data.counters.name =
+ lwc_string_ref(c->data.counters.name);
+ c->data.counters.sep =
+ lwc_string_ref(c->data.counters.sep);
+ break;
+ default:
+ break;
+ }
+ }
+
+ style->i.uncommon->content = content;
+
+ /* Free existing array */
+ if (oldcontent != NULL) {
+ for (c = oldcontent;
+ c->type != CSS_COMPUTED_CONTENT_NONE; c++) {
+ switch (c->type) {
+ case CSS_COMPUTED_CONTENT_STRING:
+ lwc_string_unref(c->data.string);
+ break;
+ case CSS_COMPUTED_CONTENT_URI:
+ lwc_string_unref(c->data.uri);
+ break;
+ case CSS_COMPUTED_CONTENT_ATTR:
+ lwc_string_unref(c->data.attr);
+ break;
+ case CSS_COMPUTED_CONTENT_COUNTER:
+ lwc_string_unref(c->data.counter.name);
+ break;
+ case CSS_COMPUTED_CONTENT_COUNTERS:
+ lwc_string_unref(c->data.counters.name);
+ lwc_string_unref(c->data.counters.sep);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (oldcontent != content)
+ free(oldcontent);
+ }
+
+ return CSS_OK;
+}'''
+
+get_side = '''\
+static inline uint8_t get_{0}(
+ const css_computed_style *style,
+ css_fixed *length, css_unit *unit)
+{{
+ uint32_t bits = style->i.bits[{1}_INDEX];
+ bits &= {1}_MASK;
+ bits >>= {1}_SHIFT;
+
+ /* 7bits: uuuuutt : units | type */
+ if ((bits & 0x3) == CSS_{1}_SET) {{
+ *length = style->i.{0};
+ *unit = bits >> 2;
+ }}
+
+ return (bits & 0x7);
+}}
+static inline uint8_t get_{0}_bits(
+ const css_computed_style *style)
+{{
+ uint32_t bits = style->i.bits[{1}_INDEX];
+ bits &= {1}_MASK;
+ bits >>= {1}_SHIFT;
+
+ /* 7bits: uuuuutt : units | type */
+ return bits;
+}}'''
+overrides['get']['top'] = get_side.format('top', 'TOP')
+overrides['get']['right'] = get_side.format('right', 'RIGHT')
+overrides['get']['bottom'] = get_side.format('bottom', 'BOTTOM')
+overrides['get']['left'] = get_side.format('left', 'LEFT')
diff --git a/src/select/select_config.py b/src/select/select_config.py
new file mode 100644
index 0000000..468e8dc
--- /dev/null
+++ b/src/select/select_config.py
@@ -0,0 +1,176 @@
+# This file is part of LibCSS.
+# Licensed under the MIT License,
+# http://www.opensource.org/licenses/mit-license.php
+# Copyright 2017 Lucas Neves <lcneves@gmail.com>
+
+# Configuration of CSS values.
+# The tuples in this set will be unpacked as arguments to the CSSValue
+# class.
+# Args: see docstring for class CSSValue in select_generator.py.
+values = {
+ ('length', 'css_fixed', 4, '0',
+ 'unit', 'css_unit', 5, 'CSS_UNIT_PX'),
+ ('integer', 'int32_t', 4, '0'),
+ ('fixed', 'css_fixed', 4, '0'),
+ ('color', 'css_color', 4, '0'),
+ ('string', 'lwc_string*'),
+ ('string_arr', 'lwc_string**'),
+ ('counter_arr', 'css_computed_counter*'),
+ ('content_item', 'css_computed_content_item*')
+}
+
+# Configuration of property groups.
+# The tuples in these sets will be unpacked as arguments to the
+# CSSproperty class.
+# Args: see docstring for class CSSProperty in select_generator.py.
+style = {
+ # Style group, only opcode
+ ('align_content', 3),
+ ('align_items', 3),
+ ('align_self', 3),
+ ('background_attachment', 2),
+ ('background_repeat', 3),
+ ('border_collapse', 2),
+ ('border_top_style', 4),
+ ('border_right_style', 4),
+ ('border_bottom_style', 4),
+ ('border_left_style', 4),
+ ('box_sizing', 2),
+ ('caption_side', 2),
+ ('clear', 3),
+ ('direction', 2),
+ ('display', 5),
+ ('empty_cells', 2),
+ ('flex_direction', 3),
+ ('flex_wrap', 2),
+ ('float', 2),
+ ('font_style', 2),
+ ('font_variant', 2),
+ ('font_weight', 4),
+ ('justify_content', 3),
+ ('list_style_position', 2),
+ ('list_style_type', 4),
+ ('overflow_x', 3),
+ ('overflow_y', 3),
+ ('outline_style', 4),
+ ('position', 3),
+ ('table_layout', 2),
+ ('text_align', 4),
+ ('text_decoration', 5),
+ ('text_transform', 3),
+ ('unicode_bidi', 2),
+ ('visibility', 2),
+ ('white_space', 3),
+ # Style group, with additional value
+ ('background_color', 2, 'color'),
+ ('background_image', 1, 'string'),
+ ('background_position', 1, (('length',), ('length',)),
+ 'CSS_BACKGROUND_POSITION_SET'),
+ ('border_top_color', 2, 'color'),
+ ('border_right_color', 2, 'color'),
+ ('border_bottom_color', 2, 'color'),
+ ('border_left_color', 2, 'color'),
+ ('border_top_width', 3, 'length', 'CSS_BORDER_WIDTH_WIDTH'),
+ ('border_right_width', 3, 'length', 'CSS_BORDER_WIDTH_WIDTH'),
+ ('border_bottom_width', 3, 'length', 'CSS_BORDER_WIDTH_WIDTH'),
+ ('border_left_width', 3, 'length', 'CSS_BORDER_WIDTH_WIDTH'),
+ ('top', 2, 'length', 'CSS_TOP_SET', None, None, 'get'),
+ ('right', 2, 'length', 'CSS_RIGHT_SET', None, None, 'get'),
+ ('bottom', 2, 'length', 'CSS_BOTTOM_SET', None, None, 'get'),
+ ('left', 2, 'length', 'CSS_LEFT_SET', None, None, 'get'),
+ ('color', 1, 'color'),
+ ('flex_basis', 2, 'length', 'CSS_FLEX_BASIS_SET'),
+ ('flex_grow', 1, 'fixed', 'CSS_FLEX_GROW_SET'),
+ ('flex_shrink', 1, 'fixed', 'CSS_FLEX_SHRINK_SET'),
+ ('font_size', 4, 'length', 'CSS_FONT_SIZE_DIMENSION'),
+ ('height', 2, 'length', 'CSS_HEIGHT_SET'),
+ ('line_height', 2, 'length', None, None, None, 'get'),
+ ('list_style_image', 1, 'string'),
+ ('margin_top', 2, 'length', 'CSS_MARGIN_SET'),
+ ('margin_right', 2, 'length', 'CSS_MARGIN_SET'),
+ ('margin_bottom', 2, 'length', 'CSS_MARGIN_SET'),
+ ('margin_left', 2, 'length', 'CSS_MARGIN_SET'),
+ ('max_height', 2, 'length', 'CSS_MAX_HEIGHT_SET'),
+ ('max_width', 2, 'length', 'CSS_MAX_WIDTH_SET'),
+ ('min_height', 2, 'length', 'CSS_MIN_HEIGHT_SET'),
+ ('min_width', 2, 'length', 'CSS_MIN_WIDTH_SET'),
+ ('opacity', 1, 'fixed', 'CSS_OPACITY_SET'),
+ ('order', 1, 'integer', 'CSS_ORDER_SET'),
+ ('padding_top', 1, 'length', 'CSS_PADDING_SET'),
+ ('padding_right', 1, 'length', 'CSS_PADDING_SET'),
+ ('padding_left', 1, 'length', 'CSS_PADDING_SET'),
+ ('padding_bottom', 1, 'length', 'CSS_PADDING_SET'),
+ ('text_indent', 1, 'length', 'CSS_TEXT_INDENT_SET'),
+ ('vertical_align', 4, 'length', 'CSS_VERTICAL_ALIGN_SET'),
+ ('width', 2, 'length', 'CSS_WIDTH_SET'),
+ ('z_index', 2, 'integer'),
+ # Style group, arrays
+ ('font_family', 3, 'string_arr', None, None,
+ 'Encode font family as an array of string objects, terminated with a '
+ 'blank entry.'),
+ ('quotes', 1, 'string_arr', None, None,
+ 'Encode quotes as an array of string objects, terminated with a '
+ 'blank entry.')
+}
+
+page = {
+ # Page group
+ ('page_break_after', 3, None, None, 'CSS_PAGE_BREAK_AFTER_AUTO'),
+ ('page_break_before', 3, None, None, 'CSS_PAGE_BREAK_BEFORE_AUTO'),
+ ('page_break_inside', 2, None, None, 'CSS_PAGE_BREAK_INSIDE_AUTO'),
+ ('widows', 1, (('integer', '2'),), None,
+ 'CSS_WIDOWS_SET'),
+ ('orphans', 1, (('integer', '2'),), None,
+ 'CSS_ORPHANS_SET')
+}
+
+uncommon = {
+ # Uncommon group
+ ('border_spacing', 1, (('length',), ('length',)), 'CSS_BORDER_SPACING_SET',
+ 'CSS_BORDER_SPACING_SET'),
+ ('break_after', 4, None, None, 'CSS_BREAK_AFTER_AUTO'),
+ ('break_before', 4, None, None, 'CSS_BREAK_BEFORE_AUTO'),
+ ('break_inside', 4, None, None, 'CSS_BREAK_INSIDE_AUTO'),
+ ('clip', 6, (('length',), ('length',), ('length',), ('length',)),
+ 'CSS_CLIP_RECT', 'CSS_CLIP_AUTO', None, ('get', 'set')),
+ ('column_count', 2, 'integer', None, 'CSS_COLUMN_COUNT_AUTO'),
+ ('column_fill', 2, None, None, 'CSS_COLUMN_FILL_BALANCE'),
+ ('column_gap', 2, 'length',
+ 'CSS_COLUMN_GAP_SET', 'CSS_COLUMN_GAP_NORMAL'),
+ ('column_rule_color', 2, 'color', None,
+ 'CSS_COLUMN_RULE_COLOR_CURRENT_COLOR'),
+ ('column_rule_style', 4, None, None, 'CSS_COLUMN_RULE_STYLE_NONE'),
+ ('column_rule_width', 3, 'length',
+ 'CSS_COLUMN_RULE_WIDTH_WIDTH', 'CSS_COLUMN_RULE_WIDTH_MEDIUM'),
+ ('column_span', 2, None, None, 'CSS_COLUMN_SPAN_NONE'),
+ ('column_width', 2, 'length',
+ 'CSS_COLUMN_WIDTH_SET', 'CSS_COLUMN_WIDTH_AUTO'),
+ ('letter_spacing', 2, 'length',
+ 'CSS_LETTER_SPACING_SET', 'CSS_LETTER_SPACING_NORMAL'),
+ ('outline_color', 2, 'color',
+ 'CSS_OUTLINE_COLOR_COLOR', 'CSS_OUTLINE_COLOR_INVERT'),
+ ('outline_width', 3, 'length',
+ 'CSS_OUTLINE_WIDTH_WIDTH', 'CSS_OUTLINE_WIDTH_MEDIUM'),
+ ('word_spacing', 2, 'length',
+ 'CSS_WORD_SPACING_SET', 'CSS_WORD_SPACING_NORMAL'),
+ ('writing_mode', 2, None, None, 'CSS_WRITING_MODE_HORIZONTAL_TB'),
+ # Uncommon group, arrays
+ ('counter_increment', 1, 'counter_arr', None, 'CSS_COUNTER_INCREMENT_NONE',
+ 'Encode counter_increment as an array of name, value pairs, '
+ 'terminated with a blank entry.'),
+ ('counter_reset', 1, 'counter_arr', None, 'CSS_COUNTER_RESET_NONE',
+ 'Encode counter_reset as an array of name, value pairs, '
+ 'terminated with a blank entry.'),
+ ('cursor', 5, 'string_arr', None, 'CSS_CURSOR_AUTO',
+ 'Encode cursor uri(s) as an array of string objects, terminated '
+ 'with a blank entry'),
+ ('content', 2, 'content_item', 'CSS_CONTENT_NORMAL', 'CSS_CONTENT_NORMAL',
+ 'Encode content as an array of content items, terminated with '
+ 'a blank entry.', 'set')
+}
+
+groups = [
+ { 'name': 'uncommon', 'props': uncommon },
+ { 'name': 'page', 'props': page },
+ { 'name': 'style', 'props': style }
+]
diff --git a/src/select/select_generator.py b/src/select/select_generator.py
new file mode 100644
index 0000000..d215f2b
--- /dev/null
+++ b/src/select/select_generator.py
@@ -0,0 +1,832 @@
+# This file is part of LibCSS.
+# Licensed under the MIT License,
+# http://www.opensource.org/licenses/mit-license.php
+# Copyright 2017 Lucas Neves <lcneves@gmail.com>
+
+import math
+import string
+import os
+from select_config import values, groups
+from assets import assets
+from overrides import overrides
+
+def get_tuple(from_var):
+ """Convert tuples, strings and None into tuple."""
+ if type(from_var) is tuple:
+ return from_var
+ elif type(from_var) is str:
+ return (from_var,)
+ elif from_var is None:
+ return ()
+ else:
+ raise TypeError('Value should be either tuple, string or None, ' +
+ 'received: ' + type(from_var).__name__)
+
+def shift_star(value_type, prop_name):
+ """Shift the asterisks from a pointer type to its name.
+
+ Example: `lwc_string** str_array` would become
+ `lwc_string **str_array`
+ """
+ star_i = value_type.find('*')
+ v_type = value_type if star_i is -1 else value_type[:star_i]
+ v_name = prop_name if star_i is -1 else value_type[star_i:] + prop_name
+ return (v_type, v_name)
+
+class Text:
+ """Class for building strings for output files."""
+ def __init__(self):
+ self._lines = []
+ self._comment = False
+ self._esc_nl = False
+ self._indent = 0
+
+ name_width = 31
+ bits_width = 16
+
+ def indent(self, value):
+ """Increase or decrease indent by <value>.
+
+ Args:
+ value <int>: positive or negative value to be added to
+ indentation.
+ """
+ self._indent += value
+
+ def comment(self):
+ """Toggle C-style comment in the output text."""
+ comm = self._comment
+ self._comment = False
+ self.append(' */' if comm else '/*')
+ self._comment = not comm
+
+ def escape_newline(self):
+ """Toggle escape of newline character."""
+ self._esc_nl = not self._esc_nl
+
+ def append(self, text=None, pre_formatted=False):
+ """Append text to file builder.
+
+ Args:
+ text:
+ <str> add contents, breaking lines and adding comment
+ markers or newline escapes as needed.
+ <list> recursively call this method for list items.
+ <falsey value> add a new line.
+ pre_formatted: just add text without preprocessing.
+ """
+ if not text:
+ self._lines.append('{}{}{}'.format(
+ '\t' * self._indent,
+ ' * ' if self._comment else '',
+ '\t' * (9 - self._indent) + '\\' if self._esc_nl else ''))
+ return
+
+ if isinstance(text, list):
+ for t in text:
+ self.append(t, pre_formatted)
+ return
+
+ if pre_formatted:
+ self._lines.append(text)
+ return
+
+ line_break_before = [ c for c in ' +/' ]
+ line_break_after = [ c for c in '({[' ]
+ column_max = 72 if self._esc_nl else 80
+ multiline = False
+
+ while text:
+ line = '\t' * self._indent
+ if self._comment:
+ line += ' * '
+ prefix_size = (3 if self._comment else 0) + 8 * self._indent
+ if prefix_size + len(text) <= column_max:
+ line += text
+ text = ''
+ else:
+ break_index = 0
+ for c in (line_break_before + line_break_after):
+ after = 1 if c in line_break_after else 0
+ break_index = max(break_index,
+ text[:column_max - prefix_size].rfind(c) + after)
+ break_index = break_index or len(text)
+ line += text[:break_index].rstrip()
+ text = text[break_index:].lstrip()
+ if self._esc_nl:
+ n_tabs = 9 - self._indent - math.floor(len(line.lstrip()) / 8)
+ line += '\t' * n_tabs + '\\'
+ self._lines.append(line)
+ if text and not self._comment and not multiline:
+ self.indent(2)
+ multiline = True
+
+ if multiline:
+ self.indent(-2)
+
+ def table_line(self):
+ """Add a sum line for the tables in computed.h"""
+ self.append('{0:{n}}{0:{b}}{0}'.format(
+ '---', n=self.name_width, b=self.bits_width))
+
+ def table_header(self):
+ """Add a header line for the tables in computed.h"""
+ self.append('{:{n}}{:{b}}{}'.format(
+ 'Property', 'Size (bits)', 'Size (bytes)',
+ n=self.name_width, b=self.bits_width))
+ self.table_line()
+
+ def result_line(self):
+ """Add a result line for the tables in computed.h"""
+ self.append(' ' * self.name_width + '=' * (self.bits_width + 3))
+
+ def to_string(self):
+ """Output contents of file builder as a string."""
+ return '\n'.join(self._lines)
+
+class CSSValue:
+ """Values to be associated with properties.
+
+ Args:
+ name <str>: value name (required).
+ css_type <str>: C type of value (required).
+ size <int>: value size, in bytes (default: None, for pointers).
+ defaults <str>: default value (default: 'NULL', for pointers).
+ The fields below are only needed if the value stores data in the
+ array of bits (currently only css_unit uses it).
+ bits_name <str>: name of bits value (default: None).
+ bits_type <str>: C type of bits value (default: None).
+ bits_size <int>: value size, in bits (default: None).
+ bits_defaults <str>: default value (default: '0').
+ """
+ def __init__(self, name, css_type, size=None, defaults='NULL',
+ bits_name=None, bits_type=None,
+ bits_size=None, bits_defaults='0'):
+ self.name = name
+ self.type = css_type
+ self.size = size # `None` means sizeof(ptr)
+ self.defaults = defaults
+ self.suffix = ''
+ self.bits = None if bits_size is None else {
+ 'name': bits_name,
+ 'type': bits_type,
+ 'size': bits_size,
+ 'defaults': bits_defaults
+ }
+
+ @property
+ def is_ptr(self):
+ """Return True if value is a pointer; False otherwise."""
+ return ((self.type, self.name) != shift_star(self.type, self.name))
+
+class CSSProperty:
+ """Class for CSS properties.
+
+ Args:
+ name <str>: property name (required).
+ type_size <int>: opcode size, in bits (required).
+ values <tuple or str>: property values (default: None).
+ To set one value, using the value's defaults:
+ 'value_name'
+ To set multiple values, using the values' defaults:
+ (('value_name',), ('value_name',))
+ To override the default of one or multiple values:
+ (('value_name', 'default'),)
+ (('value_name', 'default'), ('value_name', 'default'))
+ condition <str>: condition (opcode value) to get property
+ values in propget.h (default: None).
+ defaults <str>: default opcode (default: None)
+ comments <str>: comments for properties that are stored in
+ "struct css_computed_{group}", instead of
+ "struct css_computed_{group}_i (default: None)
+ NOTE: passing this argument will result in the property being
+ stored in "struct css_computed_{group}"!
+ overrides <tuple | str>: files for which this property shouldn't
+ autogenerate content; instead, read entry from from overrides.py
+ Possible values:
+ 'get': overrides output to autogenerated_propget.h
+ 'set': overrides output to autogenerated_propset.h
+ ('get', 'set'): overrides output to both files.
+ """
+ def __init__(self, name, type_size, values=None, condition=None,
+ defaults=None, comments=None, override=None):
+ self.name = name
+ self.type_size = type_size
+ self.values = self.make_values(values)
+ self.defaults = defaults
+ self.condition = condition
+ self.override = get_tuple(override)
+ self.comments = comments
+ self.__mask = None
+
+ def make_values(self, vals):
+ """Make list of values for this property."""
+ if vals is None:
+ return []
+ elif type(vals) is str:
+ return self.make_values(((vals,),))
+ elif type(vals) is tuple:
+ val_list = []
+ for i, v in enumerate(vals):
+ for x in values:
+ if x[0] == v[0]:
+ value = CSSValue(*x)
+ if len(v) is 2:
+ value.defaults = v[1]
+ if len(vals) > 1:
+ value.suffix = '_' + string.ascii_lowercase[i]
+ val_list.append(value)
+ break
+ else:
+ raise ValueError('Value ' + v[0] + ' not found!')
+ return val_list
+ else:
+ raise TypeError('Expected None, str or tuple, got ' +
+ type(vals).__name__)
+
+ @property
+ def bits_size(self):
+ """Size of this property in the bits array."""
+ return self.type_size + sum([ v.bits['size'] for v in self.values
+ if v.bits is not None ])
+
+ @property
+ def bytes_size(self):
+ """Size of this property's values, in bytes (excluding pointers)."""
+ return sum([ v.size for v in self.values if v.size is not None ])
+
+ @property
+ def ptr_size(self):
+ """Number of values of this property that are pointers."""
+ return sum([ 1 for v in self.values if v.size is None ])
+
+ @property
+ def size_line(self):
+ """String for computed.h with the sizes of this property."""
+ name = '{:{width}}'.format(self.name, width=Text.name_width)
+ type_size = '{:>3}'.format(str(self.type_size))
+ extra_size = sum([ v.bits['size'] for v in self.values
+ if v.bits is not None ])
+ bits_size = '{:{width}}'.format(type_size +
+ (' + ' + str(extra_size) if extra_size else ''),
+ width=Text.bits_width)
+ vars_size = '{:>3}'.format(
+ str(self.bytes_size)) if self.bytes_size else ''
+ ptr = ''
+ for v in self.values:
+ if v.size is None:
+ ptr = 'sizeof(ptr)'
+ break
+
+ return (name + bits_size + vars_size +
+ (' + ' if vars_size and ptr else '') + ptr)
+
+ @property
+ def mask(self):
+ """Getter for the bitwise mask of this property in the bits array."""
+ if self.__mask is None:
+ raise NameError('Attribute `mask` not set yet!')
+ return '0x{:x}'.format(self.__mask).lower()
+
+ @mask.setter
+ def mask(self, val):
+ """Setter for the bitwise mask of this property in the bits array."""
+ if type(val) is not int:
+ raise TypeError('Value of `mask` must be an integer!')
+ if val < 0:
+ raise ValueError('Value of `mask` must be zero or positive!')
+ self.__mask = val
+
+ @property
+ def def_undefs(self):
+ """Return defines and undefs for propget.h and propset.h."""
+ defines = [
+ '#define {}_INDEX {}'.format(self.name.upper(), self.index),
+ '#define {}_SHIFT {}'.format(self.name.upper(), self.shift),
+ '#define {}_MASK {}'.format(self.name.upper(), self.mask)
+ ]
+ undefs = [
+ '#undef {}_INDEX'.format(self.name.upper()),
+ '#undef {}_SHIFT'.format(self.name.upper()),
+ '#undef {}_MASK'.format(self.name.upper())
+ ]
+ return (defines, undefs)
+
+ def get_param_values(self, pointer=False):
+ """Make parameters for functions in propget.h and propset.h.
+
+ Args:
+ pointer <bool>: add a star before value name.
+ """
+ vals = []
+ for v in self.values:
+ vt, vn = shift_star(v.type, v.name)
+ vn += v.suffix
+ if pointer:
+ vn = '*' + vn
+ if v.name == 'counter_arr' or v.name == 'content_item':
+ vt = 'const ' + vt
+ vals.append((vt, vn))
+ if v.bits is not None:
+ bt, bn = shift_star(v.bits['type'], v.bits['name'])
+ bn += v.suffix
+ if pointer:
+ bn = '*' + bn
+ vals.append((bt, bn))
+ return vals
+
+ def get_bits(self):
+ """Make vars for the bitwise operations in propget.h and propset.h."""
+ bits = [
+ { 'letter': v.suffix[1] if v.suffix else v.bits['name'][0],
+ 'name': v.bits['name'] + v.suffix,
+ 'size': v.bits['size'] }
+ for v in self.values if v.bits is not None ]
+ bits.append({ 'letter': 't', 'size': self.type_size, 'name': 'type' })
+ bits_len = sum([ x['size'] for x in bits ])
+ comment = '/* {}bit{}: {} : {} */'.format(
+ bits_len,
+ ('' if bits_len is 1 else 's'),
+ ''.join([ b['letter'] * b['size'] for b in bits ]),
+ ' | '.join([ b['name'] for b in bits ]))
+ rev_bits = list(reversed(bits))
+ type_mask = '0x{:x}'.format(
+ sum([ 2 ** x for x in range(rev_bits[0]['size']) ])).lower()
+ shift_list = [ (x['name'],
+ sum([ b['size'] for b in rev_bits[:(i + 1)] ]),
+ sum([ 2 ** x for x in range(x['size']) ]) * 2 **
+ sum([ b['size'] for b in rev_bits[:(i + 1)] ]))
+ for i, x in enumerate(rev_bits[1:]) ]
+ return (type_mask, shift_list, comment)
+
+class Bin:
+ """The storage unit for the bits array of properties."""
+ def __init__(self, first_object):
+ self.contents = [ first_object ]
+
+ @property
+ def size(self):
+ return sum([ x.bits_size for x in self.contents ])
+
+ def push(self, obj):
+ self.contents.append(obj)
+
+class CSSGroup:
+ """Group of CSS properties (i.e. style, page, uncommon).
+
+ Args:
+ config <tuple>: imported from select_config.py.
+ """
+ def __init__(self, config):
+ self.name = config['name']
+ self.props = [ CSSProperty(*x) for x in config['props'] ]
+ self.__bits_array = None
+
+ @property
+ def bits_size(self):
+ """Sum of all property bits in the bits array."""
+ return sum([ p.bits_size for p in self.props ])
+
+ @property
+ def bytes_size(self):
+ """Sum of all property value bytes (excluded pointers)."""
+ return sum([ p.bytes_size for p in self.props ])
+
+ @property
+ def ptr_size(self):
+ """Sum of all property pointers."""
+ return sum([ p.ptr_size for p in self.props ])
+
+ @property
+ def bits_array(self):
+ """Implement a `best fit first` heuristics for the bin packing
+ of property bits in the bits array."""
+
+ if self.__bits_array is not None:
+ return self.__bits_array
+
+ bin_size = 32 # We're using uint32_t as concrete bins.
+ self.__bits_array = []
+ props = sorted(self.props, key=(lambda x: x.bits_size), reverse=True)
+
+ for p in props:
+ for b in self.__bits_array:
+ if b.size + p.bits_size <= bin_size:
+ b.push(p)
+ p.shift = (bin_size -
+ sum([ x.bits_size for x in b.contents ]))
+ break
+ else:
+ p.shift = bin_size - p.bits_size
+ self.__bits_array.append(Bin(p))
+
+ p.mask = (sum([ 2 ** x for x in range(p.bits_size) ]) *
+ 2 ** p.shift)
+ self.__bits_array.sort(key=(lambda x: x.size), reverse=True)
+
+ for i, b in enumerate(self.__bits_array):
+ for p in b.contents:
+ p.index = i
+
+ return self.__bits_array
+
+ def get_idot_grp(self):
+ """Make parameters for accessing bits and values in this group."""
+ i_dot = '' if self.name is 'page' else 'i.'
+ grp = '' if self.name is 'style' else '->{}{}'.format(
+ '' if self.name is 'page' else i_dot, self.name)
+ return (i_dot, grp)
+
+ def make_computed_h(self):
+ """Output this group's text for the computed.h file."""
+ t = Text()
+ t.append()
+
+ typedef = 'typedef ' if self.name is 'page' else ''
+ t.append('{}struct css_computed_{}{} {{'.format(
+ typedef, self.name, '' if self.name is 'page' else '_i'))
+
+ t.comment()
+ commented = []
+ t.table_header()
+
+ for prop in sorted(self.props, key=(lambda x: x.name)):
+ if prop.comments is None:
+ t.append(prop.size_line)
+ else:
+ commented.extend(( '', prop.comments, '', prop.size_line ))
+
+ t.append(commented)
+ t.append()
+ t.table_line()
+ t.append('{:{len_1}}{:>3}{:{len_2}}{:>3}{}{}'.format('',
+ str(self.bits_size), ' bits', str(self.bytes_size),
+ ' + ' + str(self.ptr_size) + 'sizeof(ptr)'
+ if self.ptr_size else '',
+ ' bytes',
+ len_1=Text.name_width, len_2=(Text.bits_width - 3)))
+ t.result_line()
+ t.append('{:{len_1}}{:>3}{}{}'.format('',
+ math.ceil(self.bits_size / 8) + self.bytes_size,
+ ' + ' + str(self.ptr_size) + 'sizeof(ptr)'
+ if self.ptr_size else '',
+ ' bytes', len_1=Text.name_width))
+ t.append()
+
+ t.append('Bit allocations:')
+ for i, b in enumerate(self.bits_array):
+ bits = []
+ for prop in b.contents:
+ for char in prop.name + prop.name.upper():
+ if char not in bits and char in string.ascii_letters:
+ bits.extend(char * prop.bits_size)
+ break
+ t.append()
+ t.append('{:<2} {:.<32}'.format(str(i), ''.join(bits)))
+ t.append('; '.join([ p.name for p in b.contents ]))
+ t.comment()
+
+ t.indent(1)
+ t.append('uint32_t bits[' + str(len(self.bits_array)) + '];')
+ t.append()
+ t.append(self.make_value_declaration(for_commented=False))
+
+ if self.name is 'style':
+ t.append()
+ for g in css_groups:
+ if g.name is not 'style' and g.name is not 'page':
+ t.append('css_computed_{0} *{0};'.format(g.name))
+ t.append('void *aural;')
+
+ t.indent(-1)
+ t.append('}}{};'.format(
+ ' css_computed_' + self.name if typedef else ''))
+
+ if self.name is not 'page':
+ typedef = 'typedef ' if self.name is not 'style' else ''
+ t.append()
+ t.append('{}struct css_computed_{} {{'.format(
+ typedef, self.name))
+ t.indent(1)
+ t.append('struct css_computed_' + self.name + '_i i;')
+ t.append()
+ t.append(self.make_value_declaration(for_commented=True))
+ t.append()
+
+ if self.name is 'style':
+ t.append('css_computed_page *page;')
+
+ t.append('struct css_computed_' + self.name + ' *next;')
+ t.append('uint32_t count;')
+ t.append('uint32_t bin;')
+ t.indent(-1)
+ t.append('}}{};'.format(
+ ' css_computed_' + self.name if typedef else ''))
+
+ return t.to_string()
+
+ def make_propset_h(self):
+ """Output this group's property functions for the propset.h file.
+
+ If group is not `style`, will also output the defaults
+ and the ENSURE_{group} texts.
+ """
+ t = Text()
+ i_dot, grp = self.get_idot_grp()
+
+ if self.name is not 'style':
+ t.append('static const css_computed_{0} default_{0} = {{'.format(
+ self.name))
+ t.indent(1)
+
+ if self.name is not 'page':
+ t.append('.i = {')
+ t.indent(1)
+
+ t.append('.bits = {')
+ t.indent(1)
+
+ bits_ops = []
+ for b in self.bits_array:
+ or_ops = []
+ for p in b.contents:
+ or_ops.append('({} << {})'.format(p.defaults, str(p.shift))
+ if p.shift else p.defaults)
+ bits_ops.append(' | '.join(or_ops))
+
+ t.append(',\n'.join(bits_ops).split('\n'))
+ t.indent(-1)
+ t.append('},')
+ t.append(',\n'.join(
+ self.make_value_declaration(False, True)).split('\n'))
+
+ if self.name is not 'page':
+ t.indent(-1)
+ t.append('},')
+ t.append(',\n'.join(
+ self.make_value_declaration(True, True) +
+ [ '.next = NULL', '.count = 0', '.bin = UINT32_MAX' ]
+ ).split('\n'))
+
+ t.indent(-1)
+ t.append('};')
+
+ t.append()
+ t.escape_newline()
+ t.append('#define ENSURE_{} do {{'.format(self.name.upper()))
+ t.indent(1)
+ t.append('if (style->{}{} == NULL) {{'.format(i_dot, self.name))
+ t.indent(1)
+ t.append('style->{}{n} = malloc(sizeof(css_computed_{n}));'.format(
+ i_dot, n=self.name))
+ t.append('if (style->{}{} == NULL)'.format(i_dot, self.name))
+ t.indent(1)
+ t.append('return CSS_NOMEM;')
+ t.indent(-1)
+ t.append()
+ t.append('memcpy(style->{}{n}, &default_{n}, '
+ 'sizeof(css_computed_{n}));'.format(i_dot, n=self.name))
+ t.indent(-1)
+ t.append('}')
+ t.indent(-1)
+ t.append('} while(0)')
+ t.escape_newline()
+ t.append()
+
+ for p in sorted(self.props, key=(lambda x: x.name)):
+ defines, undefs = p.def_undefs
+
+ t.append()
+ t.append(defines)
+
+ if p.name in overrides['set']:
+ t.append(overrides['set'][p.name], pre_formatted=True)
+ t.append(undefs)
+ continue
+
+ vals = p.get_param_values()
+ params = ', '.join([ 'css_computed_style *style', 'uint8_t type' ]
+ + [ ' '.join(x) for x in vals ])
+ t.append()
+ t.append('static inline css_error set_{}({})'.format(
+ p.name, params))
+ t.append('{')
+ t.indent(1)
+
+ t.append('uint32_t *bits;')
+ t.append()
+
+ if self.name is not 'style':
+ t.append('ENSURE_{};'.format(self.name.upper()))
+ t.append()
+
+ t.append('bits = &style{}->{}bits[{}_INDEX];'.format(
+ grp, i_dot, p.name.upper()))
+ t.append()
+
+ type_mask, shift_list, bits_comment = p.get_bits()
+ t.append(bits_comment)
+ type_mask = '(type & {})'.format(type_mask)
+ val_list = [ '({} << {})'.format(x[0], x[1]) for x in shift_list ]
+ ops_str = ' | '.join([ type_mask ] + val_list)
+ t.append('*bits = (*bits & ~{0}_MASK) | '
+ '({1}{2}{3} << {0}_SHIFT);'.format(
+ p.name.upper(),
+ '(' if val_list else '',
+ ops_str,
+ ')' if val_list else ''))
+
+ t.append()
+ for v in p.values:
+ old_n = 'old_' + v.name + v.suffix
+ old_t, old_n_shift = shift_star(v.type, old_n)
+
+ if v.name is 'string':
+ t.append('{} {} = style{}->{}{} = {};'.format(
+ old_t, old_n_shift,
+ grp, i_dot, p.name + v.suffix, v.name + v.suffix))
+ t.append()
+ t.append('if ({} != NULL) {{'.format(v.name + v.suffix))
+ t.indent(1)
+ t.append('style{}->{}{} = lwc_string_ref({});'.format(
+ grp, i_dot, p.name + v.suffix, v.name + v.suffix))
+ t.indent(-1)
+ t.append('} else {')
+ t.indent(1)
+ t.append('style{}->{}{} = NULL;'.format(
+ grp, i_dot, p.name + v.suffix))
+ t.indent(-1)
+ t.append('}')
+ t.append()
+ t.append('if ({} != NULL)'.format(old_n))
+ t.indent(1)
+ t.append('lwc_string_unref({});'.format(old_n))
+ t.indent(-1)
+
+ elif v.name is 'string_arr' or v.name is 'counter_arr':
+ iter_var = 's' if v.name is 'string_arr' else 'c'
+ iter_deref = '*s' if v.name is 'string_arr' else 'c->name'
+ t.append('{} {} = style{}->{};'.format(
+ old_t, old_n_shift,
+ grp, p.name + v.suffix))
+ t.append('{} {};'.format(old_t,
+ shift_star(v.type, iter_var)[1]))
+ t.append()
+ t.append('for ({0} = {2}; {0} != NULL && '
+ '{1} != NULL; {0}++)'.format(iter_var, iter_deref,
+ v.name + v.suffix))
+ t.indent(1)
+ t.append('{0} = lwc_string_ref({0});'.format(iter_deref))
+ t.indent(-1)
+ t.append()
+ t.append('style{}->{} = {};'.format(
+ grp, p.name + v.suffix, v.name + v.suffix))
+ t.append()
+ t.append('/* Free existing array */')
+ t.append('if ({} != NULL) {{'.format(old_n))
+ t.indent(1)
+ t.append('for ({0} = {2}; {1} != NULL; {0}++)'.format(
+ iter_var, iter_deref, old_n))
+ t.indent(1)
+ t.append('lwc_string_unref({});'.format(iter_deref))
+ t.indent(-1)
+ t.append()
+ t.append('if ({} != {})'.format(old_n, v.name + v.suffix))
+ t.indent(1)
+ t.append('free({});'.format(old_n))
+ t.indent(-2)
+ t.append('}')
+
+ elif not v.is_ptr:
+ t.append('style{}->{}{} = {};'.format(
+ grp, i_dot, p.name + v.suffix, v.name + v.suffix))
+
+ else:
+ raise ValueError('Cannot handle value ' + v.name +'!')
+
+ t.append()
+ t.append('return CSS_OK;')
+ t.indent(-1)
+ t.append('}')
+ t.append(undefs)
+
+ return t.to_string()
+
+ def make_propget_h(self):
+ """Output this group's property functions for the propget.h file."""
+ t = Text()
+ i_dot, grp = self.get_idot_grp()
+
+ for p in sorted(self.props, key=(lambda x: x.name)):
+ defines, undefs = p.def_undefs
+
+ t.append()
+ t.append(defines)
+
+ if p.name in overrides['get']:
+ t.append(overrides['get'][p.name], pre_formatted=True)
+ t.append(undefs)
+ continue
+
+ vals = p.get_param_values(pointer=True)
+ params = ', '.join([ 'css_computed_style *style' ]
+ + [ ' '.join(x) for x in vals ])
+ t.append('static inline uint8_t get_{}(const {})'.format(
+ p.name, params))
+ t.append('{')
+ t.indent(1)
+
+ if self.name is not 'style':
+ t.append('if (style{} != NULL) {{'.format(grp))
+ t.indent(1)
+
+ t.append('uint32_t bits = style{}->{}bits[{}_INDEX];'.format(
+ grp, i_dot, p.name.upper()))
+ t.append('bits &= {}_MASK;'.format(p.name.upper()))
+ t.append('bits >>= {}_SHIFT;'.format(p.name.upper()))
+ t.append()
+
+ type_mask, shift_list, bits_comment = p.get_bits()
+ t.append(bits_comment)
+
+ if p.condition:
+ t.append('if ((bits & {}) == {}) {{'.format(
+ type_mask, p.condition))
+ t.indent(1)
+
+ for v in p.values:
+ this_idot = '' if v.is_ptr and v.name != 'string' else i_dot
+ t.append('*{} = style{}->{}{};'.format(
+ v.name + v.suffix, grp, this_idot, p.name + v.suffix))
+ for i, v in enumerate(list(reversed(shift_list))):
+ if i is 0:
+ t.append('*{} = bits >> {};'.format(v[0], v[1]))
+ else:
+ t.append('*{} = (bits & 0x{:x}) >> {};'.format(
+ v[0], v[2], v[1]).lower())
+
+ if p.condition:
+ t.indent(-1)
+ t.append('}')
+
+ t.append()
+ t.append('return (bits & {});'.format(type_mask))
+
+ if self.name is not 'style':
+ t.indent(-1)
+ t.append('}')
+ t.append()
+ t.append('/* Initial value */')
+ for v in p.values:
+ t.append('*{} = {};'.format(v.name + v.suffix, v.defaults))
+ if v.bits is not None:
+ t.append('*{} = {};'.format(
+ v.bits['name'] + v.suffix, v.bits['defaults']))
+ t.append('return {};'.format(p.defaults))
+
+ t.indent(-1)
+ t.append('}')
+ t.append(undefs)
+
+ return t.to_string()
+
+ def make_value_declaration(self, for_commented, defaults=False):
+ """Output declarations of values for this group's properties.
+
+ Args:
+ for_commented: only parse values that have a `comment` field
+ defaults: outputs default value assignments.
+ """
+
+ r = []
+ for p in sorted(self.props, key=(lambda x: x.name)):
+ if bool(p.comments) == for_commented:
+ for v in p.values:
+ if defaults:
+ r.append('.{}{} = {}'.format(p.name, v.suffix,
+ v.defaults))
+ else:
+ v_type, v_name = shift_star(v.type, p.name)
+ r.append('{} {}{};'.format(v_type, v_name, v.suffix))
+ return r
+
+ def make_text(self, filename):
+ """Return this group's text for the given file."""
+ if filename == 'computed.h':
+ return self.make_computed_h()
+ elif filename == 'propset.h':
+ return self.make_propset_h()
+ elif filename == 'propget.h':
+ return self.make_propget_h()
+ else:
+ raise ValueError()
+
+css_groups = [ CSSGroup(g) for g in groups ]
+dir_path = os.path.dirname(os.path.realpath(__file__))
+
+for k, v in assets.items():
+ # Key is filename string (e.g. "computed.h") without autogenerated_ prefix
+ body = '\n'.join([ g.make_text(k) for g in css_groups ])
+ text = '\n'.join([ v['header'], body, v['footer'] ])
+ with open(os.path.join(dir_path, 'autogenerated_') + k, 'w') as file_k:
+ file_k.write(text)