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
, output
=None):
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
58 output
= pre_state
.out
59 pre(output
, pre_state
.pre_globals
, path
, mode
=mode
)
61 def include_py(path
, var_dict
=None):
62 include(path
, var_dict
, mode
=PRE
)
64 ################################################################################
65 ## Parser functions ############################################################
66 ################################################################################
77 DELIMS
= [PRE_START
, PRE_END
, DEF_START
, DEF_END
, QUOTE_H_START
, QUOTE_H_END
,
78 QUOTE_CONT
, QUOTE_END
]
82 def __init__(self
, mode
, file, out
):
84 self
.quote_blocks
= []
90 self
.last_quote
= False
98 # Flush anything from the last mode
99 if len(self
.mode
) >= 1:
100 self
.flush(self
.mode
[-1])
102 self
.mode
.append(mode
)
104 self
.cur_block
.append([])
106 self
.quote_blocks
.append([])
109 mode
= self
.mode
.pop()
111 s
= self
.quote_fn(self
.quote_blocks
.pop())
118 def flush(self
, mode
):
119 global output_line_nos
120 block
= ''.join(self
.cur_block
.pop())
121 self
.cur_block
.append([])
125 s
= 'emit(%s)\n' % repr(block
)
129 s
= 'emit(%s)\n' % block
130 elif mode
== QUOTE_H
:
131 self
.quote_blocks
[-1].append(block
)
137 s
= 'emit("\\n#line %s\\n")\n' % self
.lineno
141 # Execute the python code
142 if QUOTE
in self
.mode
:
143 self
.quote_blocks
[-1].append(s
)
146 exec(s
, self
.pre_globals
)
148 print('Exception in code:\n%s' % s
)
151 def quote_fn(self
, blocks
):
153 body
= ''.join(blocks
[1:])
155 header
= '%s:\n' % header
156 header
= self
.fix_ws(header
)
160 body
= self
.fix_ws(body
)
163 return '\n'.join([header
, body
])
165 # Fix the indenting of a block to be at the global scope
166 def fix_ws(self
, block
):
167 lines
= block
.split('\n')
175 pre
= re
.match('\\s*', line
).group(0)
179 if x
>= len(line
) or line
[x
] != pre
[x
]:
183 # Re-indent the lines to match the indent level
184 lines
= [line
[l
:] if line
.strip() else line
for line
in lines
]
185 lines
= [' '*self
.indent
+ line
for line
in lines
]
187 return '%s\n' % '\n'.join(lines
)
190 # Just add a character to a buffer
192 state
.cur_block
[-1] += [s
]
193 if state
.mode
[-1] == QUOTE
and s
:
194 s
= 'emit(%s)\n' % repr(s
)
195 state
.quote_blocks
[-1].append(s
)
197 def tokenize(s
, delims
):
203 if i
!= -1 and (idx
is None or i
< idx
):
215 def pre(out
, pre_globals
, file, mode
=NORMAL
):
218 # Set up the state of the parser
219 state
= ParserState(mode
, file, out
)
221 # Set up globals for the pre-space
222 state
.pre_globals
= pre_globals
224 # Set the global state so functions in this module can use it while being
225 # called from the preprocessed code. We back up the old state since we can
226 # preprocess recursively (through includes)
227 old_state
= pre_state
230 # Open the file for reading
231 with
open(file, 'rt') as f
:
233 for tok
in tokenize(c
, DELIMS
):
234 state
.lineno
+= tok
.count('\n')
235 # Regular preprocessed sections
241 elif tok
== DEF_START
:
246 elif tok
== QUOTE_H_START
:
248 elif tok
== QUOTE_H_END
and state
.mode
[-1] == QUOTE_H
:
251 elif tok
== QUOTE_CONT
and state
.mode
[-1] == QUOTE
:
254 elif tok
== QUOTE_END
:
259 # Finish up: flush the last block of characters
262 # Restore the old parser state
263 pre_state
= old_state
266 if len(sys
.argv
) < 3:
267 print('Usage: %s [options] <input> <output> [var=value...]' % sys
.argv
[0])
272 output_line_nos
= False
275 if sys
.argv
[1] == '-d':
277 sys
.argv
[1:] = sys
.argv
[3:]
278 elif sys
.argv
[1] == '-l':
279 output_line_nos
= True
280 sys
.argv
[1:] = sys
.argv
[2:]
284 # Set up input/output files
288 # Wrapper class for passing stuff to the program
291 # Loop over all key=value pairs and set these variables.
293 for opt
in sys
.argv
[3:]:
294 key
, _
, value
= opt
.partition('=')
295 variables
[key
] = value
298 p
.variables
= variables
300 # Preprocessor globals. This keeps the state of the preprocessed blocks
304 'include_py' : include_py
,
308 # Run the preprocessor
309 with
open(o
, 'wt') as out
:
310 pre(out
, pre_globals
, i
)
313 with
open(depend
, 'wt') as d_file
:
314 d_file
.write('%s: %s\n' % (o
, ' '.join(depend_files
)))