--- /dev/null
+#!/usr/bin/env python3
+
+# Copyright (C) 2018 Nick Downing <nick@ndcode.org>
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; version 2.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51
+# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+import re
+import sys
+
+if len(sys.argv) >= 2:
+ package_name = '{0:s}.'.format(sys.argv[1])
+else:
+ package_name = ''
+
+default_value = {
+ 'bool': 'False',
+ 'int': '-1',
+ 'ref': 'None',
+ 'str': '\'\'',
+ 'list(bool)': '[]',
+ 'list(int)': '[]',
+ 'list(ref)': '[]',
+ 'list(str)': '[]',
+ 'set(bool)': 'set()',
+ 'set(int)': 'set()',
+ 'set(ref)': 'set()',
+ 'set(str)': 'set()'
+}
+default_value_str = {
+ 'bool': 'false',
+ 'int': '-1',
+ 'ref': '-1',
+ 'str': ''
+}
+
+re_class = re.compile(
+ '([\t ]*)class ([A-Za-z_][A-Za-z0-9_]*)(\([A-Za-z_][A-Za-z0-9_.]*\))?:'
+)
+re_element = re.compile(
+ '([\t ]+)# GENERATE ELEMENT\((([^()]|\([^()]*\))*)\)( BEGIN)?'
+)
+re_factory = re.compile(
+ '([\t ]*)# GENERATE FACTORY\(([^()]*)\)( BEGIN)?'
+)
+re_method = re.compile(
+ '([\t ]+)def ([A-Za-z_][A-Za-z0-9_]*)\('
+)
+re_attribute = re.compile(
+ '([\t ]+)([A-Za-z_][A-Za-z0-9_]*) = (.*)'
+)
+re_comment = re.compile(
+ '([\t ]+)# (.*)'
+)
+stack = []
+classes = []
+base_classes = [{'element.Element': []}] # params
+method = {}
+
+sys.stdout.write('%{\n')
+had_blank = True
+
+lines = []
+delayed_line = ''
+
+line = sys.stdin.readline()
+while len(line):
+ match = re_class.match(line)
+ if match is not None:
+ indent = match.group(1)
+ class_name = match.group(2)
+ base_class = match.group(3)
+ if base_class is None:
+ base_class = ''
+ else:
+ base_class = base_class[1:-1]
+ while len(stack) and stack[-1][0][:len(indent)] == indent:
+ #_ , temp_class_name, _, _ = stack.pop()
+ #for temp_base_class, temp_fields in base_classes.pop().items():
+ # base_classes[-1][
+ # '{0:s}.{1:s}'.format(temp_class_name, temp_base_class)
+ # ] = temp_fields
+ temp_indent, _, _, _ = stack.pop()
+ if len(delayed_line):
+ lines.append('{0:s};\n'.format(delayed_line[:-3]))
+ delayed_line = ''
+ else:
+ lines.append('{0:s}}};\n'.format(temp_indent))
+ for i in range(len(base_classes) - 1, -1, -1):
+ if base_class in base_classes[i]:
+ classes.append(
+ '.'.join([j for _, j, _, _ in stack] + [class_name])
+ )
+ full_base_class = '.'.join(
+ [j for _, j, _, _ in stack[:i]] + [base_class]
+ )
+ base_classes[-1][class_name] = list(base_classes[i][base_class])
+ break
+ else:
+ full_base_class = base_class
+ stack.append((indent, class_name, base_class, full_base_class))
+ base_classes.append({})
+ lines.append(delayed_line)
+ delayed_line = '{0:s}class {1:s}{2:s} {{\n'.format(
+ indent,
+ class_name,
+ ': ' + base_class if len(base_class) else ''
+ )
+ line = sys.stdin.readline()
+ else:
+ match = re_element.match(line)
+ if match is not None:
+ indent = match.group(1)
+ params = match.group(2)
+ begin = match.group(4)
+
+ while len(stack) and stack[-1][0][:len(indent)] == indent:
+ #_, temp_class_name, _, _ = stack.pop()
+ #for temp_base_class, temp_fields in base_classes.pop().items():
+ # base_classes[-1][
+ # '{0:s}.{1:s}'.format(temp_class_name, temp_base_class)
+ # ] = temp_fields
+ temp_indent, _, _, _ = stack.pop()
+ if len(delayed_line):
+ lines.append('{0:s};\n'.format(delayed_line[:-3]))
+ delayed_line = ''
+ else:
+ lines.append('{0:s}}};\n'.format(temp_indent))
+ _, class_name, base_class, full_base_class = stack[-1]
+
+ fields = params.split(',')
+ if fields[-1] == '':
+ del fields[-1:]
+ fields = [i.split() for i in fields]
+ fields = [(type, name) for [type, name] in fields]
+ #i = len(base_classes[-2][class_name])
+ #base_classes[-2][class_name].extend(fields)
+
+ if len(fields):
+ lines.append(delayed_line)
+ delayed_line = ''
+ for type, name in fields:
+ lines.append(
+ '{0:s}{1:s} {2:s} = {3:s};\n'.format(
+ indent,
+ type,
+ name,
+ default_value[type]
+ )
+ )
+
+ if begin is not None:
+ line = sys.stdin.readline()
+ while len(line):
+ if line.strip() == '# GENERATE END':
+ break
+ line = sys.stdin.readline()
+ else:
+ assert False
+ line = sys.stdin.readline()
+ else:
+ match = re_factory.match(line)
+ if match is not None:
+ indent = ' ' #match.group(1)
+ param = match.group(2)
+ begin = match.group(3)
+
+ if not had_blank:
+ sys.stdout.write('\n')
+ sys.stdout.write(
+ '''{0:s}def factory(tag, attrib = {{}}, *args, **kwargs):
+{1:s} return tag_to_class.get(tag, {2:s})(tag, attrib, *args, **kwargs)
+'''.format(
+ indent,
+ indent,
+ param,
+ indent
+ )
+ )
+ had_blank = True
+
+ if begin is not None:
+ line = sys.stdin.readline()
+ while len(line):
+ if line.strip() == '# GENERATE END':
+ break
+ line = sys.stdin.readline()
+ else:
+ assert False
+ line = sys.stdin.readline()
+ else:
+ match = re_method.match(line)
+ if match is not None:
+ indent = match.group(1)
+ name = match.group(2)
+
+ if name not in method:
+ method[name] = []
+ method[name].extend(
+ [
+ '@method({0:s})\n'.format('.'.join([i for _, i, _, _ in stack])),
+ line[len(indent):]
+ ]
+ )
+ line = sys.stdin.readline()
+ line_rstrip = line.rstrip()
+ while (
+ len(line) and
+ line_rstrip[:len(indent)] == indent[:len(line_rstrip)] and
+ (len(line_rstrip) <= len(indent) or line[len(indent)] in '\t )')
+ ):
+ method[name].append(line[len(indent):])
+ line = sys.stdin.readline()
+ line_rstrip = line.rstrip()
+ else:
+ match = re_attribute.match(line)
+ if match is not None:
+ indent = match.group(1)
+ name = match.group(2)
+ value = match.group(3)
+
+ if name not in method:
+ method[name] = []
+ method[name].append(
+ '{0:s}.{1:s} = {2:s}\n'.format(
+ '.'.join([i for _, i, _, _ in stack]),
+ name,
+ value
+ )
+ )
+ line = sys.stdin.readline()
+ else:
+ match = re_comment.match(line)
+ if match is not None:
+ indent = match.group(1)
+ text = match.group(2)
+
+ while len(stack) and stack[-1][0][:len(indent)] == indent:
+ #_, temp_class_name, _, _ = stack.pop()
+ #for temp_base_class, temp_fields in base_classes.pop().items():
+ # base_classes[-1][
+ # '{0:s}.{1:s}'.format(temp_class_name, temp_base_class)
+ # ] = temp_fields
+ temp_indent, _, _, _ = stack.pop()
+ if len(delayed_line):
+ lines.append('{0:s};\n'.format(delayed_line[:-3]))
+ delayed_line = ''
+ else:
+ lines.append('{0:s}}};\n'.format(temp_indent))
+
+ lines.append(delayed_line)
+ delayed_line = ''
+ lines.append('{0:s}/* {1:s} */\n'.format(indent, text))
+
+ line = sys.stdin.readline()
+ else:
+ line_rstrip = line.rstrip()
+ if len(line_rstrip):
+ sys.stdout.write(' {0:s}\n'.format(line_rstrip))
+ had_blank = False
+ elif not had_blank:
+ sys.stdout.write('\n')
+ had_blank = True
+ line = sys.stdin.readline()
+
+while len(stack):
+ #_ , temp_class_name, _, _ = stack.pop()
+ #for temp_base_class, temp_fields in base_classes.pop().items():
+ # base_classes[-1][
+ # '{0:s}.{1:s}'.format(temp_class_name, temp_base_class)
+ # ] = temp_fields
+ temp_indent, _, _, _ = stack.pop()
+ if len(delayed_line):
+ lines.append('{0:s};\n'.format(delayed_line[:-3]))
+ delayed_line = ''
+ else:
+ lines.append('{0:s}}};\n'.format(temp_indent))
+lines.append(delayed_line)
+
+sys.stdout.write(
+ '%}}\n\n%%\n\n{0:s}\n%%\n{1:s}'.format(
+ ''.join(lines),
+ ''.join(
+ [
+ '\n{0:s}{1:s}'.format(
+ ''.join(lines),
+ (
+ 'del {0:s}\n'.format(name)
+ if any([i[:1] == '@' for i in lines]) else
+ ''
+ )
+ )
+ for name, lines in method.items()
+ ]
+ )
+ )
+)