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
)
152 #body = 'emit(%s)\n' % repr(body)
154 body
= self
.fix_ws(body
)
157 return '\n'.join([header
, body
])
159 # Fix the indenting of a block to be at the global scope
160 def fix_ws(self
, block
):
161 lines
= block
.split('\n')
169 pre
= re
.match('\\s*', line
).group(0)
173 if x
>= len(line
) or line
[x
] != pre
[x
]:
177 # Re-indent the lines to match the indent level
178 lines
= [line
[l
:] if line
.strip() else line
for line
in lines
]
179 lines
= [' '*self
.indent
+ line
for line
in lines
]
181 return '%s\n' % '\n'.join(lines
)
184 # Just add a character to a buffer
186 state
.cur_block
[-1] += [s
]
187 if QUOTE
== state
.mode
[-1] and s
:
188 s
= 'emit(%s)\n' % repr(s
)
189 state
.quote_blocks
[-1].append(s
)
191 def tokenize(s
, delims
):
198 if i
!= -1 and (idx
is None or i
< idx
):
203 tokens
.append(s
[:idx
])
212 def pre(out
, pre_globals
, file, mode
=NORMAL
):
215 # Set up the state of the parser
216 state
= ParserState(mode
)
219 state
.last_quote
= False
223 # Set up globals for the pre-space
224 state
.pre_globals
= pre_globals
226 # Set the global state so functions in this module can use it while being
227 # called from the preprocessed code. We back up the old state since we can
228 # preprocess recursively (through includes)
229 old_state
= pre_state
232 # Open the file for reading
233 with
open(file, 'rt') as f
:
235 #tokens = re.findall(pattern, c, re.DOTALL) # DOTALL means keep newlines
236 tokens
= tokenize(c
, DELIMS
)
239 # Regular preprocessed sections
245 elif tok
== DEF_START
:
250 elif tok
== QUOTE_H_START
:
252 elif tok
== QUOTE_H_END
and state
.mode
[-1] == QUOTE_H
:
255 elif tok
== QUOTE_CONT
and state
.mode
[-1] == QUOTE
:
258 elif tok
== QUOTE_END
:
263 # Finish up: flush the last block of characters
266 # Restore the old parser state
267 pre_state
= old_state
270 if len(sys
.argv
) < 3:
271 print('Usage: %s [options] <input> <output> [var=value...]' % sys
.argv
[0])
278 if sys
.argv
[1] == '-d':
280 sys
.argv
[1:] = sys
.argv
[3:]
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 if os
.path
.isfile(depend
):
314 with
open(depend
, 'rt') as d_file
:
315 lines
= d_file
.readlines()
316 lines
= [l
for l
in lines
if l
.strip() and l
[:l
.find(':')] != o
]
320 line
= '%s: %s' % (o
, ' '.join(depend_files
))
323 with
open(depend
, 'wt') as d_file
:
324 d_file
.write('\n'.join(lines
))