1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
|
#!/usr/bin/python3
#-*- coding:utf-8 -*-
# Copyright © 2009, 2011-2014, 2016-2017 B. Clausius <barcc@gmx.de>
#
# 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, either version 3 of the License, or
# (at your option) any later version.
#
# 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, see <http://www.gnu.org/licenses/>.
import sys, os, re
from contextlib import contextmanager
from .pxmakros import PxMakros
from pybiklib.debug import DEBUG_NOLINENO
class Py2pyxError (Exception): pass
class OpenWriteLazy:
def __init__(self, path):
self.path = path
self.delayed_text = ''
self.file = None
def write_delayed(self, text):
self.delayed_text += text
def write(self, text):
if self.file is None:
if self.path:
print('generating', self.path)
self.file = open(self.path, 'wt', encoding='utf-8')
else:
self.file = sys.stdout
if self.delayed_text:
self.file.write(self.delayed_text)
self.delayed_text = ''
self.file.write(text)
def close(self):
if self.file is not None:
self.file.close()
class PxParser:
# groups in match objects and patterns
# 1: leading space
# 2: px command (includes 3 and 4)
# 3: pxd flag
# 4: px key
# 5: px argument (includes 6 and 7)
# 6: makro name
# 7: makro argument
re_pxline = re.compile(r'^( *)(#px *([a-z]?)(.)|)((\w*)\s*(.*?))\n$', flags=re.ASCII)
# key: px command
# value:
# pattern for pyx-file or None (copy line),
# count next lines or "count" (use px argument),
fdict = {
'+': (r'\5', 0),
'/': (r'\5', 1),
'>': (r'#\2\5', 0),
'-': (r'#\2\5', 1),
':': (r'#\2\5', '*'),
'.': (r'#\2\5', 0),
}
fdict['#'] = fdict['>']
char_to_makro = { 'd': 'PXDFILE',
'h': 'HFILE',
'c': 'CFILE',
'': '',
}
def __init__(self):
self.next_cntlines = 0
def parse_line(self, line):
makro = None
makrospec = None
makroarg = None
match = self.re_pxline.match(line)
if match.group(2):
if self.next_cntlines == 0:
# start px command
makro = match.group(3)
if makro == 'm':
makro = match.group(6)
makrospec = match.expand(r'\2\6')
if not makro:
raise Py2pyxError('makro name expected')
makroarg = match.group(7)
else:
makro = self.char_to_makro[makro]
makrospec = match.group(2)
makroarg = match.group(5)
elif self.next_cntlines == 1:
raise Py2pyxError('#px-command found, normal line expected')
elif self.next_cntlines == '*':
# end px command
if match.group(4) != '.':
raise Py2pyxError('closing #px-command or normal line expected')
if match.group(3):
raise Py2pyxError('closing #px-command with makro')
makro = ''
else:
assert False
pyxpat, self.next_cntlines = self.fdict[match.group(4)]
elif self.next_cntlines == 0:
# echo normal line
makro = ''
pyxpat = r'\2\5'
self.next_cntlines = 0
elif self.next_cntlines == 1:
# oneline px block
makroarg = match.group(5)
pyxpat = r'#\2\5'
self.next_cntlines = 0
elif self.next_cntlines == '*':
# multiline px block
makroarg = match.group(5)
pyxpat = r'#\2\5'
else:
assert False
indent = match.group(1)
pyxline = match.expand(pyxpat)
return indent, makro, makrospec, makroarg, pyxline
class PxGenerator (PxMakros):
def __init__(self, pyxf, pxdf, pxhf, pxcf):
self.pyxf = pyxf
self.pxdf = pxdf
self.pxhf = pxhf
self.pxcf = pxcf
self.pyx_indent = ''
self.pxd_indent = ''
self.px_skiplines = False
self.makro = None
self.makrolineno = None
self.pyxline = None
self.namespace = None
def PYXFILE(self, line):
if self.pyxline is None:
self.pyxline = line
elif not line.startswith('#'):
if self.pyxline.startswith('#'):
self.pyxline = line
else:
raise Py2pyxError('multiple pyx-file lines for source line not allowed: {!r}'.format(line))
def PYXFILE_finalize(self, indent):
assert self.pyxline is not None
if not self.pyxline or self.pyxline[0] == '#':
self.makrospec = ''
self.pyxf.write(self.pyx_indent + indent + self.pyxline + self.makrospec +'\n')
self.pyxline = None
def generate_lines(self, lineno, indent, makro, makrospec, makroarg, pyxline):
if DEBUG_NOLINENO:
lineno = 'X'
self.makrospec = (' '+makrospec) if makrospec else ''
if self.px_skiplines:
pyxline = '#~ ' + pyxline
self.PYXFILE(pyxline)
if makro is not None:
self.makro = getattr(self, makro, makro)
self.makrolineno = 0
self.makrodata = None
else:
self.makrolineno += 1
if self.makro == 'IF':
bvalue = eval(makroarg)
assert type(bvalue) is bool
self.px_skiplines = not bvalue
elif self.makro == 'IF_END':
self.px_skiplines = False
elif self.px_skiplines:
pass
elif self.makro == 'INDENT':
self.pyx_indent = ' '
elif self.makro == 'DEDENT':
self.pyx_indent = ''
elif self.makro == 'DINDENT':
self.pxd_indent = ' '
elif self.makro == 'DDEDENT':
self.pxd_indent = ''
elif callable(self.makro):
if self.makro(indent, makroarg, lineno):
self.makro = None
elif self.makro:
raise Py2pyxError('unknown makro: {!s}({!r})'.format(self.makro, makroarg))
self.PYXFILE_finalize(indent)
def create_pyx(src_path, pyx_path, pxd_path, pxh_path, pxc_path):
errors = 0
openr = lambda path: open(path, 'rt', encoding='utf-8')
@contextmanager
def openw(path):
owl = OpenWriteLazy(path)
try:
yield owl
finally:
owl.close
with openr(src_path) as srcf, openw(pyx_path) as pyxf, openw(pxd_path) as pxdf, openw(pxh_path) as pxhf, openw(pxc_path) as pxcf:
pxdf.write_delayed('#generated from: {}\n\n'.format(src_path))
pxhf.write_delayed('//generated from: {}\n\n'.format(src_path))
pxcf.write_delayed('//generated from: {}\n\n'.format(src_path))
pxparser = PxParser()
pxgenerator = PxGenerator(pyxf, pxdf, pxhf, pxcf)
for lineno, line in enumerate(srcf):
try:
indent, makro, makrospec, makroarg, pyxline = pxparser.parse_line(line)
except Py2pyxError as e:
print('%s:%d:'%(src_path, lineno+1), e, file=sys.stderr)
print(' invalid line:', repr(line), file=sys.stderr)
indent = ''
makro = makrospec = makroarg = None
pyxline = ''
errors += 1
except Exception as e:
print('%s:%d:'%(src_path, lineno+1), e, file=sys.stderr)
print(' line:', repr(line), file=sys.stderr)
raise
try:
pxgenerator.generate_lines(lineno+1, indent, makro, makrospec, makroarg, pyxline)
except Py2pyxError as e:
print('%s:%d:'%(src_path, lineno+1), e, file=sys.stderr)
print(' invalid line:', repr(line), file=sys.stderr)
errors += 1
except Exception as e:
print('%s:%d:'%(src_path, lineno+1), e, file=sys.stderr)
print(' line:', repr(line), file=sys.stderr)
raise
if errors:
raise Py2pyxError('create_pyx failed with %s errors' % errors)
def main(argv):
if not (1 < len(argv) <= 5):
print('usage:', os.path.basename(__file__), 'python-file [pyx-filename [pxd-filename [h-filename [c-filename]]]]',
file=sys.stderr)
return 1
arg_src = argv[1]
arg_dst = argv[2] if len(argv) > 2 else None
arg_pxd = argv[3] if len(argv) > 3 else None
arg_pxh = argv[4] if len(argv) > 4 else None
arg_pxc = argv[5] if len(argv) > 5 else None
if not os.path.exists(arg_src):
print('error:', arg_src, 'does not exist', file=sys.stderr)
return 1
create_pyx(arg_src, arg_dst, arg_pxd, arg_pxh, arg_pxc)
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))
|