1 ################################################################################
2 ## Prethon-Python-based preprocessor.
4 ## Copyright 2011 Zach Wegner
6 ## This file is part of Prethon.
8 ## Prethon is free software: you can redistribute it and/or modify
9 ## it under the terms of the GNU General Public License as published by
10 ## the Free Software Foundation, either version 3 of the License, or
11 ## (at your option) any later version.
13 ## Prethon is distributed in the hope that it will be useful,
14 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ## GNU General Public License for more details.
18 ## You should have received a copy of the GNU General Public License
19 ## along with Prethon. If not, see <http://www.gnu.org/licenses/>.
20 ################################################################################
29 # This state is used by the preprocessor external functions. The preprocessor
30 # uses its own local state for the parsing, but the preprocessed code needs
31 # access (through this module) to this state.
35 NORMAL
, PRE
, DEF
, QUOTE_H
, QUOTE
= range(5)
37 ################################################################################
38 ## Preprocessor functions ######################################################
39 ################################################################################
41 # Emit function. This is what preprocessor code uses to emit real code.
44 pre_state
.out
.write(str(s
))
46 # Include: Recursively call the preprocessor
47 def include(path
, var_dict
=None, mode
=NORMAL
):
48 global pre_state
, depend_files
49 depend_files
+= [path
]
51 vd
= pre_state
.variables
.copy()
52 for key
, value
in var_dict
.items():
54 pre_state
.variables
= vd
55 pre(pre_state
.out
, pre_state
.pre_globals
, path
, mode
=mode
)
57 def include_py(path
, var_dict
=None):
58 include(path
, var_dict
, mode
=PRE
)
60 ################################################################################
61 ## Parser functions ############################################################
62 ################################################################################
73 DELIMS
= [PRE_START
, PRE_END
, DEF_START
, DEF_END
, QUOTE_H_START
, QUOTE_H_END
,
74 QUOTE_CONT
, QUOTE_END
]
78 def __init__(self
, mode
):
80 self
.quote_blocks
= []
88 # Flush anything from the last mode
89 if len(self
.mode
) >= 1:
90 self
.flush(self
.mode
[-1])
92 self
.mode
.append(mode
)
94 self
.cur_block
.append([])
96 self
.quote_blocks
.append([])
99 mode
= self
.mode
.pop()
101 s
= self
.quote_fn(self
.quote_blocks
.pop())
108 def flush(self
, mode
):
109 block
= ''.join(self
.cur_block
.pop())
110 self
.cur_block
.append([])
114 s
= 'emit(%s)\n' % repr(block
)
120 s
= 'emit(%s)\n' % block
122 elif mode
== QUOTE_H
:
124 self
.quote_blocks
[-1].append(s
)
132 # Execute the python code
133 if QUOTE
in self
.mode
:
134 self
.quote_blocks
[-1].append(s
)
137 exec(s
, self
.pre_globals
)
139 print('Exception in code:\n%s' % s
)
142 def quote_fn(self
, blocks
):
144 body
= ''.join(blocks
[1:])
146 header
= '%s:\n' % header
147 header
= self
.fix_ws(header
)
150 #body = 'emit(%s)\n' % repr(body)
152 body
= self
.fix_ws(body
)
155 return '\n'.join([header
, body
])
157 # Fix the indenting of a block to be at the global scope
158 def fix_ws(self
, block
):
159 lines
= block
.split('\n')
167 pre
= re
.match('\\s*', line
).group(0)
171 if x
>= len(line
) or line
[x
] != pre
[x
]:
175 # Re-indent the lines to match the indent level
176 lines
= [line
[l
:] if line
.strip() else line
for line
in lines
]
177 lines
= [' '*self
.indent
+ line
for line
in lines
]
179 return '%s\n' % '\n'.join(lines
)
182 # Just add a character to a buffer
184 state
.cur_block
[-1] += [s
]
185 if QUOTE
== state
.mode
[-1] and s
:
186 s
= 'emit(%s)\n' % repr(s
)
187 state
.quote_blocks
[-1].append(s
)
189 def tokenize(s
, delims
):
196 if i
!= -1 and (idx
is None or i
< idx
):
201 tokens
.append(s
[:idx
])
210 def pre(out
, pre_globals
, file, mode
=NORMAL
):
213 # Set up the state of the parser
214 state
= ParserState(mode
)
217 state
.last_quote
= False
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 #tokens = re.findall(pattern, c, re.DOTALL) # DOTALL means keep newlines
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
))