1 ################################################################################
3 ## Prethon-Python-based preprocessor.
5 ## Copyright 2011 Zach Wegner
7 ## This file is part of Prethon.
9 ## Prethon is free software: you can redistribute it and/or modify
10 ## it under the terms of the GNU General Public License as published by
11 ## the Free Software Foundation, either version 3 of the License, or
12 ## (at your option) any later version.
14 ## Prethon is distributed in the hope that it will be useful,
15 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ## GNU General Public License for more details.
19 ## You should have received a copy of the GNU General Public License
20 ## along with Prethon. If not, see <http://www.gnu.org/licenses/>.
22 ################################################################################
31 # This state is used by the preprocessor external functions. The preprocessor
32 # uses its own local state for the parsing, but the preprocessed code needs
33 # access (through this module) to this state.
37 NORMAL
, PRE
, DEF
, QUOTE_H
, QUOTE
= range(5)
39 ################################################################################
40 ## Preprocessor functions ######################################################
41 ################################################################################
43 # Emit function. This is what preprocessor code uses to emit real code.
46 pre_state
.out
.write(str(s
))
48 # Include: Recursively call the preprocessor
49 def include(path
, var_dict
=None, mode
=NORMAL
):
50 global pre_state
, depend_files
51 depend_files
+= [path
]
53 vd
= pre_state
.variables
.copy()
54 for key
, value
in var_dict
.items():
56 pre_state
.variables
= vd
57 pre(pre_state
.out
, pre_state
.pre_globals
, path
, mode
=mode
)
59 def include_py(path
, var_dict
=None):
60 include(path
, var_dict
, mode
=PRE
)
62 ################################################################################
63 ## Parser functions ############################################################
64 ################################################################################
75 DELIMS
= [PRE_START
, PRE_END
, DEF_START
, DEF_END
, QUOTE_H_START
, QUOTE_H_END
,
76 QUOTE_CONT
, QUOTE_END
]
80 def __init__(self
, mode
):
82 self
.quote_blocks
= []
90 # Flush anything from the last mode
91 if len(self
.mode
) >= 1:
92 self
.flush(self
.mode
[-1])
94 self
.mode
.append(mode
)
96 self
.cur_block
.append([])
98 self
.quote_blocks
.append([])
101 mode
= self
.mode
.pop()
103 s
= self
.quote_fn(self
.quote_blocks
.pop())
110 def flush(self
, mode
):
111 block
= ''.join(self
.cur_block
.pop())
112 self
.cur_block
.append([])
116 s
= 'emit(%s)\n' % repr(block
)
122 s
= 'emit(%s)\n' % block
124 elif mode
== QUOTE_H
:
126 self
.quote_blocks
[-1].append(s
)
134 # Execute the python code
135 if QUOTE
in self
.mode
:
136 self
.quote_blocks
[-1].append(s
)
139 exec(s
, self
.pre_globals
)
141 print('Exception in code:\n%s' % s
)
144 def quote_fn(self
, blocks
):
146 body
= ''.join(blocks
[1:])
148 header
= '%s:\n' % header
149 header
= self
.fix_ws(header
)
153 body
= self
.fix_ws(body
)
156 return '\n'.join([header
, body
])
158 # Fix the indenting of a block to be at the global scope
159 def fix_ws(self
, block
):
160 lines
= block
.split('\n')
168 pre
= re
.match('\\s*', line
).group(0)
172 if x
>= len(line
) or line
[x
] != pre
[x
]:
176 # Re-indent the lines to match the indent level
177 lines
= [line
[l
:] if line
.strip() else line
for line
in lines
]
178 lines
= [' '*self
.indent
+ line
for line
in lines
]
180 return '%s\n' % '\n'.join(lines
)
183 # Just add a character to a buffer
185 state
.cur_block
[-1] += [s
]
186 if state
.mode
[-1] == QUOTE
and s
:
187 s
= 'emit(%s)\n' % repr(s
)
188 state
.quote_blocks
[-1].append(s
)
190 def tokenize(s
, delims
):
197 if i
!= -1 and (idx
is None or i
< idx
):
202 tokens
.append(s
[:idx
])
211 def pre(out
, pre_globals
, file, mode
=NORMAL
):
214 # Set up the state of the parser
215 state
= ParserState(mode
)
218 state
.last_quote
= False
222 # Set up globals for the pre-space
223 state
.pre_globals
= pre_globals
225 # Set the global state so functions in this module can use it while being
226 # called from the preprocessed code. We back up the old state since we can
227 # preprocess recursively (through includes)
228 old_state
= pre_state
231 # Open the file for reading
232 with
open(file, 'rt') as f
:
234 tokens
= tokenize(c
, DELIMS
)
237 # Regular preprocessed sections
243 elif tok
== DEF_START
:
248 elif tok
== QUOTE_H_START
:
250 elif tok
== QUOTE_H_END
and state
.mode
[-1] == QUOTE_H
:
253 elif tok
== QUOTE_CONT
and state
.mode
[-1] == QUOTE
:
256 elif tok
== QUOTE_END
:
261 # Finish up: flush the last block of characters
264 # Restore the old parser state
265 pre_state
= old_state
268 if len(sys
.argv
) < 3:
269 print('Usage: %s [options] <input> <output> [var=value...]' % sys
.argv
[0])
276 if sys
.argv
[1] == '-d':
278 sys
.argv
[1:] = sys
.argv
[3:]
282 # Set up input/output files
286 # Wrapper class for passing stuff to the program
289 # Loop over all key=value pairs and set these variables.
291 for opt
in sys
.argv
[3:]:
292 key
, _
, value
= opt
.partition('=')
293 variables
[key
] = value
296 p
.variables
= variables
298 # Preprocessor globals. This keeps the state of the preprocessed blocks
302 'include_py' : include_py
,
306 # Run the preprocessor
307 with
open(o
, 'wt') as out
:
308 pre(out
, pre_globals
, i
)
311 if os
.path
.isfile(depend
):
312 with
open(depend
, 'rt') as d_file
:
313 lines
= d_file
.readlines()
314 lines
= [l
for l
in lines
if l
.strip() and l
[:l
.find(':')] != o
]
318 line
= '%s: %s' % (o
, ' '.join(depend_files
))
321 with
open(depend
, 'wt') as d_file
:
322 d_file
.write('\n'.join(lines
))