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