1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3 4# 5# Copyright 2017, Data61 6# Commonwealth Scientific and Industrial Research Organisation (CSIRO) 7# ABN 41 687 119 230. 8# 9# This software may be distributed and modified according to the terms of 10# the BSD 2-Clause license. Note that NO WARRANTY is provided. 11# See "LICENSE_BSD2.txt" for details. 12# 13# @TAG(DATA61_BSD) 14# 15 16''' 17Stage 0 parsers. These parsers are various alternatives for the first step in 18the CAmkES parsing pipeline. A stage 0 parser makes the following 19transformation: 20 21 string/file ��� augmented_input 22''' 23 24from __future__ import absolute_import, division, print_function, \ 25 unicode_literals 26from camkes.internal.seven import cmp, filter, map, zip 27 28from .base import Parser 29from .exception import ParseError 30import codecs 31import os 32import re 33import shutil 34import subprocess 35 36 37class CPP(Parser): 38 ''' 39 An alternative to opening and reading a file that calls the C 40 pre-processor. 41 ''' 42 43 def __init__(self, cpp_bin='cpp', flags=None): 44 self.cpp_bin = cpp_bin 45 self.flags = flags or [] 46 self.out_dir = os.path.join(os.getcwd(), 'camkes-tool') 47 if not os.path.isdir(self.out_dir): 48 os.mkdir(self.out_dir) 49 50 def parse_file(self, filename): 51 # Run cpp with -MD to generate dependencies because we want to 52 # track what files it read. 53 output_basename = os.path.join(self.out_dir, os.path.basename(filename)) 54 output = output_basename + '.cpp' 55 deps = output_basename + '.d' 56 p = subprocess.Popen([self.cpp_bin, '-MD', '-MF', deps, '-o', 57 output] + self.flags + [filename], stdout=subprocess.PIPE, 58 stderr=subprocess.PIPE, universal_newlines=True) 59 _, stderr = p.communicate() 60 if p.returncode != 0: 61 raise ParseError('CPP failed: %s' % stderr) 62 with codecs.open(output, 'r', 'utf-8') as f: 63 processed = f.read() 64 with codecs.open(deps, 'r', 'utf-8') as f: 65 read = set(parse_makefile_rule(f)) 66 return processed, set([filename]) | read 67 68 def parse_string(self, string): 69 output_basename = os.path.join(self.out_dir, 'output.camkes') 70 output = output_basename + '.cpp' 71 deps = output_basename + '.d' 72 p = subprocess.Popen([self.cpp_bin, '-MD', '-MF', deps, '-o', 73 output] + self.flags, stdin=subprocess.PIPE, 74 stdout=subprocess.PIPE, stderr=subprocess.PIPE, 75 universal_newlines=True) 76 # hack around python2 and 3's awful unicode problems 77 try: 78 string = str(string) 79 except UnicodeEncodeError: 80 # str will fail on python2 as it is ascii only. 81 # however the below fails on python3. So here we are. 82 string = string.encode('utf-8') 83 _, stderr = p.communicate(string) 84 if p.returncode != 0: 85 raise ParseError('CPP failed: %s' % stderr) 86 with codecs.open(output, 'r', 'utf-8') as f: 87 processed = f.read() 88 with codecs.open(deps, 'r', 'utf-8') as f: 89 read = set(parse_makefile_rule(f)) 90 return processed, read 91 92 93class Reader(Parser): 94 ''' 95 A basic "parser" that just opens and reads the contents of a file. 96 ''' 97 98 def parse_file(self, filename): 99 with codecs.open(filename, 'r', 'utf-8') as f: 100 return f.read(), set([filename]) 101 102 def parse_string(self, string): 103 return string, set() 104 105 106def parse_makefile_rule(f): 107 ''' 108 Parse a dependency rule generated by the C pre-processor and return the 109 dependencies of the rule. 110 ''' 111 in_deps = False 112 for line in f: 113 line = line.strip() 114 if line == '': 115 continue 116 if not in_deps: 117 head = re.match(r'.*?:(.*)$', line) 118 if head is None: 119 raise ParseError('unexpected dependency line %s found' % line) 120 in_deps = True 121 line = head.group(1) 122 for dep in line.split(): 123 if dep != '\\': 124 yield dep 125 if not line.endswith('\\'): 126 return 127