diff options
Diffstat (limited to 'tools/generation/lib/template.py')
-rw-r--r-- | tools/generation/lib/template.py | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/tools/generation/lib/template.py b/tools/generation/lib/template.py new file mode 100644 index 000000000..fa891f00b --- /dev/null +++ b/tools/generation/lib/template.py @@ -0,0 +1,160 @@ +# Copyright (C) 2016 the V8 project authors. All rights reserved. +# This code is governed by the BSD license found in the LICENSE file. + +import os, re +import codecs, yaml + +from util.find_comments import find_comments +from util.parse_yaml import parse_yaml +from test import Test + +indentPattern = re.compile(r'^(\s*)') +interpolatePattern = re.compile(r'\{\s*(\S+)\s*\}') + +def indent(text, prefix = ' '): + '''Prefix a block of text (as defined by the "line break" control + character) with some character sequence.''' + + if isinstance(text, list): + lines = text + else: + lines = text.split('\n') + + return prefix + ('\n' + prefix).join(lines) + +class Template: + def __init__(self, filename): + self.filename = filename + + with open(filename) as template_file: + self.source = template_file.read() + + self.attribs = dict() + self.regions = [] + + self._parse() + + def _remove_comment(self, comment): + '''Create a region that is not intended to be referenced by any case, + ensuring that the comment is not emitted in the rendered file.''' + name = '__remove_comment_' + str(comment['firstchar']) + '__' + + # When a removed comment ends the line, the following newline character + # should also be removed from the generated file. + lastchar = comment['lastchar'] + if self.source[lastchar] == '\n': + comment['lastchar'] = comment['lastchar'] + 1 + + self.regions.insert(0, dict(name=name, **comment)) + + def _parse(self): + for comment in find_comments(self.source): + meta = parse_yaml(comment['source']) + + # Do not emit the template's frontmatter in generated files + # (file-specific frontmatter is generated as part of the rendering + # process) + if meta: + self.attribs['meta'] = meta + self._remove_comment(comment) + continue + + # Do not emit license information in generated files (recognized as + # comments preceeding the YAML frontmatter) + if not self.attribs.get('meta'): + self._remove_comment(comment) + continue + + match = interpolatePattern.match(comment['source']) + + if match == None: + continue + + self.regions.insert(0, dict(name=match.group(1), **comment)) + + def expand_regions(self, source, context): + lines = source.split('\n') + + for region in self.regions: + whitespace = indentPattern.match(lines[region['lineno']]).group(1) + value = context['regions'].get(region['name'], '') + source = source[:region['firstchar']] + \ + indent(value, whitespace).lstrip() + \ + source[region['lastchar']:] + + setup = context['regions'].get('setup') + + if setup: + source = setup + '\n' + source + + teardown = context['regions'].get('teardown') + + if teardown: + source += '\n' + teardown + '\n' + + return source + + def _frontmatter(self, case_filename, case_values): + description = case_values['meta']['desc'].strip() + \ + ' (' + self.attribs['meta']['name'].strip() + ')' + lines = [] + + lines += [ + '// This file was procedurally generated from the following sources:', + '// - ' + case_filename, + '// - ' + self.filename, + '/*---', + 'description: ' + description, + ] + + esid = self.attribs['meta'].get('esid') + if esid: + lines.append('esid: ' + esid) + + es6id = self.attribs['meta'].get('es6id') + if es6id: + lines.append('es6id: ' + es6id) + + features = [] + features += case_values['meta'].get('features', []) + features += self.attribs['meta'].get('features', []) + if len(features): + lines += ['features: ' + yaml.dump(features)] + + flags = ['generated'] + flags += case_values['meta'].get('flags', []) + flags += self.attribs['meta'].get('flags', []) + lines += ['flags: ' + yaml.dump(flags).strip()] + + includes = [] + includes += case_values['meta'].get('includes', []) + includes += self.attribs['meta'].get('includes', []) + if len(includes): + lines += ['includes: ' + yaml.dump(includes).strip()] + + if case_values['meta'].get('negative'): + lines += ['negative: ' + case_values['meta'].get('negative')] + + info = [] + + if 'info' in self.attribs['meta']: + info.append(indent(self.attribs['meta']['info'])) + if 'info' in case_values['meta']: + if len(info): + info.append('') + info.append(indent(case_values['meta']['info'])) + + if len(info): + lines.append('info: >') + lines += info + + lines.append('---*/') + + return '\n'.join(lines) + + def expand(self, case_filename, case_name, case_values, encoding): + frontmatter = self._frontmatter(case_filename, case_values) + body = self.expand_regions(self.source, case_values) + + return Test(self.attribs['meta']['path'] + case_name + '.js', + source=codecs.encode(frontmatter + '\n' + body, encoding)) |