summaryrefslogtreecommitdiff
path: root/tools/generation/lib/template.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/generation/lib/template.py')
-rw-r--r--tools/generation/lib/template.py160
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))