Index: tools/nixysa/nixysa/js_utils.py |
=================================================================== |
--- tools/nixysa/nixysa/js_utils.py (revision 0) |
+++ tools/nixysa/nixysa/js_utils.py (revision 0) |
@@ -0,0 +1,674 @@ |
+#!/usr/bin/python2.4 |
+# |
+# Copyright 2008 Google Inc. |
+# |
+# Licensed under the Apache License, Version 2.0 (the "License"); |
+# you may not use this file except in compliance with the License. |
+# You may obtain a copy of the License at |
+# |
+# http://www.apache.org/licenses/LICENSE-2.0 |
+# |
+# Unless required by applicable law or agreed to in writing, software |
+# distributed under the License is distributed on an "AS IS" BASIS, |
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+# See the License for the specific language governing permissions and |
+# limitations under the License. |
+ |
+"""Utilities for Javascript code generation. |
+ |
+This module contains a few utilities for Javascript code generation. |
+""" |
+ |
+import re |
+import sys |
+import naming |
+import gflags |
+import log |
+import cpp_utils |
+import writer |
+ |
+ |
+_doxygen_tag_re = re.compile(r'\s*\\(\w+) ') |
+_param_re = re.compile(r'\s*\\param (\w+) (.*?)$') |
+_non_id_re = re.compile(r'[^A-Z0-9_]') |
+ |
+ |
+def GetScopePrefix(scope, type_defn, scope_operator): |
+ """Gets the prefix string to reference a type from a given scope. |
+ |
+ This function returns a concatenation of js scope operators such as, in the |
+ context of the given scope, when prefixing the name of the given type, it |
+ will reference exactly that type. |
+ |
+ For example, given: |
+ namespace A { |
+ namespace B { |
+ class C; |
+ } |
+ namespace D { |
+ void F(); |
+ } |
+ } |
+ To access C from F, one needs to refer to it by B::C. This function will |
+ return the 'B::' part. |
+ |
+ Args: |
+ scope: the Definition for the scope from which the type must be accessed. |
+ type_defn: the Definition for the type which must be accessed. |
+ scope_operator: the scope operator for your language, ie '.' or '::' |
+ |
+ Returns: |
+ the prefix string. |
+ """ |
+ return cpp_utils.GetScopePrefixWithScopeOperator(scope, type_defn, '.') |
+ |
+ |
+def GetScopedName(scope, type_defn): |
+ """Gets the prefix string to reference a type from a given scope. |
+ |
+ This function returns a concatenation of C++ scope operators such as, in the |
+ context of the given scope, when prefixing the name of the given type, it |
+ will reference exactly that type. |
+ |
+ For example, given: |
+ namespace A { |
+ namespace B { |
+ class C; |
+ } |
+ namespace D { |
+ void F(); |
+ } |
+ } |
+ To access C from F, one needs to refer to it by B::C. This function will |
+ return exactly that. |
+ |
+ Args: |
+ scope: the Definition for the scope from which the type must be accessed. |
+ type_defn: the Definition for the type which must be accessed. |
+ |
+ Returns: |
+ the scoped reference string. |
+ """ |
+ return GetScopePrefix(scope, type_defn) + type_defn.name |
+ |
+ |
+def GetFullyQualifiedScopePrefix(scope): |
+ """Gets the fully qualified scope prefix. |
+ |
+ Args: |
+ scope: the Definition for the scope from which the type must be accessed. |
+ |
+ Returns: |
+ the fully qualified scope prefix string. |
+ """ |
+ scope_stack = scope.GetParentScopeStack() + [scope] |
+ return '.'.join([s.name for s in scope_stack[1:]] + ['']) |
+ |
+ |
+def GetFullyQualifiedTypeName(type_defn): |
+ """Gets the fully qualified name for a type |
+ |
+ Args: |
+ type_defn: the type definition you want a name for. |
+ |
+ Returns: |
+ the fully qualified name string. |
+ """ |
+ return type_defn.binding_model.JSDocTypeString(type_defn) |
+ |
+ |
+def GetFullyQualifiedTypeString(type_defn): |
+ """ |
+ """ |
+ type_defn = type_defn.GetFinalType() |
+ type_stack = type_defn.GetParentScopeStack() |
+ name = type_defn.name |
+ return '.'.join([s.name for s in type_stack[1:]] + [name]) |
+ |
+ |
+def GetGetterName(field): |
+ """Gets the name of the getter function for a member field. |
+ |
+ Unless overridden by the 'getter' attribute in IDL, the default name for the |
+ getter function is the name of the field, normalized to the lower-case |
+ convention. |
+ |
+ Args: |
+ field: the Definition for the field. |
+ |
+ Returns: |
+ the name of the getter function. |
+ """ |
+ return (field.attributes['getter'] or naming.Normalize(field.name, |
+ naming.Lower)) |
+ |
+ |
+def GetSetterName(field): |
+ """Gets the name of the setter function for a member field. |
+ |
+ Unless overridden by the 'setter' attribute in IDL, the default name for the |
+ setter function is 'set_' concatenated with the name of the field, normalized |
+ to the lower-case convention. |
+ |
+ Args: |
+ field: the Definition for the field. |
+ |
+ Returns: |
+ the name of the setter function. |
+ """ |
+ return (field.attributes['setter'] or |
+ 'set_%s' % naming.Normalize(field.name, naming.Lower)) |
+ |
+ |
+def GetFunctionParamPrototype(scope, param): |
+ """Gets the string needed to declare a parameter in a function prototype. |
+ |
+ Args: |
+ scope: the scope of the prototype. |
+ param: the Function.Param to declare |
+ |
+ Returns: |
+ a (string, list) pair. The string is the declaration of the parameter in |
+ the prototype. The list contains (nam, Definition) pairs, describing the |
+ types that need to be forward-declared (bool is false) or defined (bool is |
+ true). |
+ """ |
+ bm = param.type_defn.binding_model |
+ if param.mutable: |
+ text, need_defn = bm.CppMutableParameterString(scope, param.type_defn) |
+ else: |
+ text, need_defn = bm.CppParameterString(scope, param.type_defn) |
+ name = naming.Normalize(param.name, naming.Java) |
+ return name, [(name, param.type_defn)] |
+ |
+ |
+def GetFunctionPrototype(scope, obj, member): |
+ """Gets the string needed to declare a function prototype. |
+ |
+ Args: |
+ scope: the scope of the prototype. |
+ obj: the function to declare. |
+ member: True if member function |
+ |
+ Returns: |
+ A string prototype. |
+ """ |
+ id_prefix = GetFullyQualifiedScopePrefix(scope) |
+ proto = '' |
+ if member: |
+ proto = 'prototype.' |
+ param_strings = [GetFunctionParamPrototype(scope, p)[0] for p in obj.params] |
+ param_string = ', '.join(param_strings) |
+ prototype = '%s%s%s = function(%s) { };' % ( |
+ id_prefix, proto, naming.Normalize(obj.name, naming.Java), param_string) |
+ return prototype |
+ |
+ |
+def GetFunctionParamType(obj, param_name): |
+ """Gets the type of a function param. |
+ |
+ Args: |
+ obj: The function. |
+ param_name: The name of the parameter. |
+ |
+ Returns |
+ A string which is the type of the parameter. |
+ """ |
+ if param_name[-1] == '?': |
+ param_name = param_name[:-1] |
+ for p in obj.params: |
+ if p.name == param_name: |
+ return GetFullyQualifiedTypeName(p.type_defn) |
+ log.SourceError(obj.source, 'No param "%s" on function "%s"' % |
+ (param_name, obj.name)) |
+ return '*' |
+ |
+ |
+def GetCommentsForParams(func): |
+ """Gets the comments for the params. |
+ |
+ Args: |
+ func: The function. |
+ param: The parameter. |
+ Returns: |
+ a (string, dict) pair. The string is the comments minus the param parts. |
+ The dict is a dict of param names to comments. |
+ """ |
+ collecting_key = None |
+ param_comments = {} |
+ comments = [] |
+ comment_lines = func.attributes['__docs'].splitlines() |
+ for line in comment_lines: |
+ match = _doxygen_tag_re.match(line) |
+ if match: |
+ if match.group(1) == 'param': |
+ match = _param_re.match(line) |
+ if match: |
+ collecting_key = match.group(1) |
+ param_comments[collecting_key] = match.group(2) |
+ else: |
+ log.SourceError(func, |
+ ('Incorrect format for param ' + |
+ 'comment for param "%s" on function "%s"') % |
+ (param_name, func.name)) |
+ else: |
+ comments += [line] |
+ collecting_key = None |
+ elif collecting_key: |
+ param_comments[collecting_key] += '\n' + line |
+ else: |
+ comments += [line] |
+ return '\n'.join(comments), param_comments |
+ |
+ |
+def GetParamSpec(obj, param_name): |
+ """Gets the parameter specification string for a function parameter. |
+ |
+ Args: |
+ obj: The function. |
+ param_name: The name of the paramter. |
+ |
+ Returns: |
+ a string in JSDOC format for the parameter. |
+ """ |
+ type = GetFunctionParamType(obj, param_name) |
+ return '@param {%s} %s ' % (type, naming.Normalize(param_name, naming.Java)) |
+ |
+ |
+def GetReturnSpec(obj, flags): |
+ """Gets the return type specification string for a function. |
+ |
+ Args: |
+ obj: The function. |
+ flags: An map of flags. The only one we care about is 'eat_lines' which |
+ we'll set to True if the 'noreturndocs' attribute exists. |
+ |
+ Returns: |
+ a string in JSDOC format for the return type. |
+ """ |
+ if gflags.FLAGS['no-return-docs'].value and 'noreturndocs' in obj.attributes: |
+ flags['eat_lines'] = True |
+ return '' |
+ if obj.type_defn: |
+ type = GetFullyQualifiedTypeName(obj.type_defn) |
+ else: |
+ type = "**unknown return type**" |
+ return '@return {%s}' % type |
+ |
+ |
+class JavascriptFileWriter(object): |
+ """Javascript file writer class. |
+ |
+ This class helps with generating a Javascript file by parts, by allowing |
+ delayed construction of 'sections' inside the code, that can be filled later. |
+ For example one can create a section for forward declarations, and add code to |
+ that section as the rest of the file gets written. |
+ |
+ It also provides facility to add #include lines, and header guards for header |
+ files, as well as simplifies namespace openning and closing. |
+ |
+ It helps 'beautifying' the code in simple cases. |
+ """ |
+ |
+ class Section(object): |
+ """Javascript writer section class.""" |
+ # this regexp is used for EmitTemplate. It maps {#SomeText} into 'section' |
+ # groups and the rest of the text into 'text' groups in the match objects. |
+ _template_re = re.compile( |
+ r""" |
+ ^\s* # skip whitespaces |
+ (?: # non grouping ( ) |
+ \$\{\#(?P<section>[_A-Za-z0-9]*)\} # matches a '${#AnyText}' section |
+ # tag, puts the 'AnyText' in a |
+ # 'section' group |
+ | # ... or ... |
+ (?P<text>.*) # matches any text, puts it into |
+ # a 'text' group |
+ ) # close non-grouping ( ) |
+ \s*$ # skip whitespaces |
+ """, re.MULTILINE | re.VERBOSE) |
+ |
+ def __init__(self, indent_string, indent): |
+ """Inits a JavascriptFileWriter.Section. |
+ |
+ Args: |
+ indent_string: the string for one indentation. |
+ indent: the number of indentations for code inside the section. |
+ """ |
+ self._indent_string = indent_string |
+ self._code = [] |
+ self._fe_namespaces = [] |
+ self._be_namespaces = [] |
+ self._section_map = {} |
+ self._indent = indent |
+ self._need_validate = False |
+ |
+ def EmitSection(self, section): |
+ """Emits a section at the current position. |
+ |
+ When calling GetLines, the code for the section passed in will be output |
+ at this position. |
+ |
+ Args: |
+ section: the section to add. |
+ """ |
+ self._ValidateNamespace() |
+ self._code.append(section) |
+ |
+ def CreateUnlinkedSection(self, name, indent=None): |
+ """Creates a section, but without emitting it. |
+ |
+ When calling GetLines, the code for the created section will not be |
+ output unless EmitSection is called. |
+ |
+ Args: |
+ name: the name of the section. |
+ indent: (optional) the number of indentations for the new section. |
+ |
+ Returns: |
+ the created section. |
+ """ |
+ if not indent: |
+ indent = self._indent |
+ section = JavascriptFileWriter.Section(self._indent_string, indent) |
+ self._section_map[name] = section |
+ return section |
+ |
+ def CreateSection(self, name): |
+ """Creates a section, and emits it at the current position. |
+ |
+ When calling GetLines, the code for the created section will be output |
+ at this position. |
+ |
+ Args: |
+ name: the name of the section. |
+ |
+ Returns: |
+ the created section. |
+ """ |
+ self._ValidateNamespace() |
+ section = self.CreateUnlinkedSection(name, indent=self._indent) |
+ self.EmitSection(section) |
+ return section |
+ |
+ def GetSection(self, name): |
+ """Gets a section by name. |
+ |
+ Args: |
+ name: the name of the section. |
+ |
+ Returns: |
+ the section if found, None otherwise. |
+ """ |
+ try: |
+ return self._section_map[name] |
+ except KeyError: |
+ return None |
+ |
+ def PushNamespace(self, name): |
+ """Opens a namespace. |
+ |
+ This function opens a namespace by emitting code at the current position. |
+ This is done lazily so that openning, closing, then openning the same |
+ namespace again doesn't add extra code. |
+ |
+ Args: |
+ name: the name of the namespace. |
+ """ |
+ self._need_validate = True |
+ self._fe_namespaces.append(name) |
+ |
+ def PopNamespace(self): |
+ """Closes the current namespace. |
+ |
+ This function closes the current namespace by emitting code at the |
+ current position. This is done lazily so that openning, closing, then |
+ openning the same namespace again doesn't add extra code. |
+ |
+ Returns: |
+ the name of the namespace that was closed. |
+ """ |
+ self._need_validate = True |
+ return self._fe_namespaces.pop() |
+ |
+ def _ValidateNamespace(self): |
+ """Validates the current namespace by emitting all the necessary code.""" |
+ if not self._need_validate: |
+ return |
+ self._need_validate = False |
+ l = cpp_utils.GetCommonPrefixLength( |
+ self._fe_namespaces, self._be_namespaces) |
+ while len(self._be_namespaces) > l: |
+ name = self._be_namespaces.pop() |
+ self._code.append('} // namespace %s' % name) |
+ for name in self._fe_namespaces[l:]: |
+ self._be_namespaces.append(name) |
+ self._code.append('namespace %s {' % name) |
+ |
+ def EmitCode(self, code): |
+ """Emits code at the current position. |
+ |
+ The code passed in will be output at the current position when GetLines |
+ is called. The code is split into lines, and re-indented to match the |
+ section indentation. |
+ |
+ Args: |
+ code: a string containing the code to emit. |
+ """ |
+ self._ValidateNamespace() |
+ for line in code.split('\n'): |
+ if not line: |
+ self._code.append('') |
+ else: |
+ self._code.append(line) |
+ |
+ def EmitTemplate(self, template): |
+ """Emits a template at the current position. |
+ |
+ Somewhat similarly to string.template.substitute, this function takes a |
+ string containing code and escape sequences. Every time an escape |
+ sequence, of the form '${#SectionName}', is found, a section is created |
+ (or re-used) and emitted at the current position. Otherwise the text is |
+ treated as code and simply emitted as-is. For example take the following |
+ string: |
+ |
+ void MyFunction() { |
+ ${#MyFunctionBody} |
+ } |
+ |
+ Calling EmitTemplate with that string is equivalent to: |
+ |
+ section.EmitCode('void MyFunction() {') |
+ section.CreateSection('MyFunctionBody') |
+ section.EmitCode('}') |
+ |
+ If a section of that particular name already exists, it is reused. |
+ |
+ Args: |
+ template: a string containing the template to emit. |
+ """ |
+ |
+ def _Match(mo): |
+ """Function called for template regexp matches. |
+ |
+ Args: |
+ mo: match object. |
+ |
+ Returns: |
+ empty string. |
+ """ |
+ section_group = mo.group('section') |
+ if section_group: |
+ if section_group in self._section_map: |
+ section = self._section_map[section_group] |
+ self.EmitSection(section) |
+ else: |
+ self.CreateSection(section_group) |
+ else: |
+ self.EmitCode(mo.group('text')) |
+ return '' |
+ self._template_re.sub(_Match, template) |
+ |
+ def IsEmpty(self): |
+ """Queries whether the section is empty or not. |
+ |
+ Returns: |
+ True if the section is empty, False otherwise. |
+ """ |
+ return not self._code |
+ |
+ def AddPrefix(self, code): |
+ """Adds code at the beginning of the section. |
+ |
+ Args: |
+ code: a single code line. |
+ """ |
+ self._code = [code] + self._code |
+ |
+ def GetLines(self): |
+ """Retrieves the full contents of the section. |
+ |
+ This function gathers all the code that was emitted, including in |
+ children sections. |
+ |
+ Returns: |
+ a list of code lines. |
+ """ |
+ # close open namespaces |
+ self._fe_namespaces = [] |
+ self._need_validate = True |
+ self._ValidateNamespace() |
+ lines = [] |
+ for line in self._code: |
+ if isinstance(line, JavascriptFileWriter.Section): |
+ lines.extend(line.GetLines()) |
+ else: |
+ lines.append(line) |
+ return lines |
+ |
+ def __init__(self, filename, is_header, header_token=None, |
+ indent_string=' '): |
+ """Inits a JavascriptFileWriter. |
+ |
+ The file writer has a 'main section' where all the code will go. See |
+ CreateSection, EmitCode. |
+ |
+ Args: |
+ filename: the name of the file. |
+ is_header: a boolean, True if this is a header file. In that case, the |
+ header guard will be generated. |
+ header_token: (optional) a string for the header guard token. Defaults to |
+ a generated one based on the filename. |
+ indent_string: (optional) the string to be used for indentations. |
+ Defaults to two spaces. |
+ """ |
+ self._filename = filename |
+ self._is_header = is_header |
+ self._header_token = '' |
+ self._includes = [] |
+ self._include_section = self.Section(indent_string, 0) |
+ self._main_section = self.Section(indent_string, 0) |
+ |
+ def AddInclude(self, name, system=False): |
+ """Adds an include to the file. |
+ |
+ Args: |
+ name: the name of the include. |
+ system: (optional) True if it is a 'system' include (uses the <file.h> |
+ syntax). Defaults to False. |
+ """ |
+ if system: |
+ self._include_section.EmitCode('#include <%s>' % name) |
+ else: |
+ self._include_section.EmitCode('#include "%s"' % name) |
+ |
+ def CreateSection(self, name): |
+ """Creates a section within the main section. |
+ |
+ Args: |
+ name: the name of the section to be created. |
+ |
+ Returns: |
+ the created section. |
+ """ |
+ return self._main_section.CreateSection(name) |
+ |
+ def GetSection(self, name): |
+ """Gets a section by name from the main section. |
+ |
+ Args: |
+ name: the name of the section to look for. |
+ |
+ Returns: |
+ the created section if found, None otherwise. |
+ """ |
+ return self._main_section.GetSection(name) |
+ |
+ def PushNamespace(self, name): |
+ """Opens a namespace in the main section. |
+ |
+ This function opens a namespace by emitting code at the current position. |
+ This is done lazily so that openning, closing, then openning the same |
+ namespace again doesn't add extra code. |
+ |
+ Args: |
+ name: the name of the namespace. |
+ """ |
+ self._main_section.PushNamespace(name) |
+ |
+ def PopNamespace(self): |
+ """Closes the current namespace in the main section. |
+ |
+ This function closes the current namespace by emitting code at the |
+ current position. This is done lazily so that openning, closing, then |
+ openning the same namespace again doesn't add extra code. |
+ |
+ Returns: |
+ the name of the namespace that was closed. |
+ """ |
+ return self._main_section.PopNamespace() |
+ |
+ def EmitCode(self, code): |
+ """Emits code at the current position in the main section. |
+ |
+ Args: |
+ code: a string containing the code to emit. |
+ """ |
+ self._main_section.EmitCode(code) |
+ |
+ def GetLines(self): |
+ """Retrieves the full contents of the file writer. |
+ |
+ This function gathers all the code that was emitted, including the |
+ header guard (if this is a header file), and the includes. |
+ |
+ Returns: |
+ a list of code lines. |
+ """ |
+ lines = [] |
+ include_lines = self._include_section.GetLines() |
+ if include_lines: |
+ lines.append('') |
+ lines.extend(include_lines) |
+ main_lines = self._main_section.GetLines() |
+ if main_lines: |
+ lines.append('') |
+ lines.extend(main_lines) |
+ return lines |
+ |
+ def Write(self): |
+ """Writes the full contents to the file. |
+ |
+ This function writes the full contents to the file specified by the |
+ 'filename' parameter at creation time. |
+ """ |
+ writer.WriteIfContentDifferent(self._filename, |
+ '\n'.join(self.GetLines()) + '\n') |
+ |
+ |
+def main(): |
+ pass |
+ |
+ |
+if __name__ == '__main__': |
+ main() |
Property changes on: tools/nixysa/nixysa/js_utils.py |
___________________________________________________________________ |
Added: svn:executable |
+ * |
Added: svn:eol-style |
+ LF |