1use crate::Config;
2use crate::FileOptions;
3use std::error::Error;
4use std::io::{Result as IoResult, Write};
5use std::path;
6use std::result::Result;
7
8#[derive(Debug, Default)]
13pub struct Convert {
14 config: Config,
15 file_options: FileOptions,
16 vars: Option<Vec<(String, String)>>,
17 preamble_py: Option<String>,
18}
19
20#[derive(Debug, PartialEq)]
21enum LineType {
22 Verilog,
23 PythonInline,
24 PythonBlock(bool), None,
26}
27
28impl Default for LineType {
29 fn default() -> Self {
30 Self::None
31 }
32}
33
34impl Convert {
35 pub fn new(
37 config: Config,
38 file_options: FileOptions,
39 vars: Option<Vec<(String, String)>>,
40 preamble_py: Option<String>,
41 ) -> Convert {
42 Convert {
43 config,
44 file_options,
45 vars,
46 preamble_py,
47 }
48 }
49
50 pub fn from_args() -> Convert {
52 let (config, file_options, vars, preamble_py) = Config::from_args();
53 Convert::new(config, file_options, vars, preamble_py)
54 }
55
56 fn open_input(&self) -> IoResult<String> {
58 std::fs::read_to_string(&self.file_options.input)
59 }
60
61 pub fn open_output(&self) -> IoResult<std::fs::File> {
65 std::fs::File::create(&self.output_python_file_name())
66 }
67
68 fn output_file_name(&self) -> String {
70 self.file_options
71 .output
72 .clone()
73 .unwrap_or_else(|| {
74 if self.file_options.output.is_some() {
75 return self.file_options.input.clone();
76 }
77 let mut output = self.file_options.input.clone();
78 let ext = path::Path::new(&output).extension().unwrap_or_default();
80 if ext.is_empty() {
81 output.push_str(".v");
82 } else {
83 output = output.replace(ext.to_str().unwrap(), "v");
84 }
85 output
86 })
87 .replace("\\", "/")
88 }
89
90 fn output_python_file_name(&self) -> String {
92 self.output_file_name() + ".py"
93 }
94
95 fn output_inst_file_name(&self) -> String {
97 self.output_file_name() + ".inst"
98 }
99
100 fn switch_line_type(&self, line_type: &mut LineType, line: &str) {
101 let trimmed_line = line.trim_start();
102 *line_type = match line_type {
103 LineType::PythonBlock(_not_first_line) => {
104 if trimmed_line == "*/" {
105 LineType::None } else {
107 LineType::PythonBlock(true)
108 }
109 }
110 _ => {
111 if trimmed_line.starts_with(&format!("/*{}", self.config.magic_comment_str)) {
112 LineType::PythonBlock(false)
113 } else if trimmed_line.starts_with(&format!("//{}", self.config.magic_comment_str))
114 {
115 LineType::PythonInline
116 } else {
117 LineType::Verilog
118 }
119 }
120 }
121 }
122
123 fn pre_process_line(&self, line: &str) -> String {
125 line.trim_end().replace(
126 "\t",
127 str::repeat(" ", self.config.tab_size as usize).as_str(),
128 )
129 }
130
131 fn escape_verilog(&self, line: &str) -> String {
133 let mut escaped_line = String::with_capacity(line.len());
134 for c in line.chars() {
135 match c {
136 '\'' => escaped_line.push_str("\\'"), '{' => escaped_line.push_str("{{"),
138 '}' => escaped_line.push_str("}}"),
139 _ => escaped_line.push(c),
140 }
141 }
142 escaped_line
143 }
144
145 fn apply_verilog_regex(&self, line: &str) -> String {
147 self.config
148 .template_re
149 .replace_all(line, format!("{{$1}}").as_str())
150 .to_string()
151 }
152
153 pub(crate) fn apply_protected_verilog_regex(&self, line: &str) -> String {
154 self.config
155 .template_re
156 .replace_all(
157 line,
158 format!("__LEFT_BRACKET__{{$1}}__RIGHT_BRACKET__").as_str(),
159 )
160 .to_string()
161 }
162
163 pub fn run_python(&self) -> IoResult<()> {
167 let py_file = self.output_python_file_name();
168 let v_file = self.output_file_name();
169 let v_file_f = std::fs::File::create(&v_file)?;
170 #[cfg(not(target_family = "windows"))]
171 let python_cmd = "python3";
172 #[cfg(target_family = "windows")]
173 let python_cmd = "python";
174 let output = std::process::Command::new(python_cmd)
175 .arg(&py_file)
176 .stdout(v_file_f)
177 .output()?;
178 if !output.status.success() {
179 return Err(std::io::Error::new(
180 std::io::ErrorKind::Other,
181 format!(
182 "Python script failed with exit code: {}\n{}",
183 output.status.code().unwrap_or(-1),
184 String::from_utf8_lossy(&output.stderr)
185 ),
186 ));
187 } else {
188 if self.config.delete_python {
189 std::fs::remove_file(&py_file)?;
190 }
191 }
192 Ok(())
193 }
194
195 #[cfg(not(feature = "inst"))]
196 fn process_python_line<W: Write>(
197 &self,
198 line: &str,
199 py_indent_prior: usize,
200 stream: &mut W,
201 ) -> Result<()> {
202 writeln!(stream, "{}", utf8_slice::from(&line, py_indent_prior))
203 }
204
205 #[cfg(feature = "macro")]
206 fn print_macros<W: Write>(&self, stream: &mut W) -> Result<(), Box<dyn Error>> {
207 let output_verilog_file_name = &self.output_file_name();
208 let output_inst_file_name = &self.output_inst_file_name();
209 let verilog_path = path::Path::new(output_verilog_file_name);
210 let inst_path = path::Path::new(output_inst_file_name);
211 writeln!(
212 stream,
213 concat!(
214 "# PyTV macros:\n",
215 "OUTPUT_VERILOG_FILE_PATH = '{}'\n",
216 "OUTPUT_VERILOG_FILE_NAME = '{}'\n",
217 "OUTPUT_VERILOG_FILE_STEM = '{}'\n",
218 "OUTPUT_INST_FILE_PATH = '{}'\n",
219 "OUTPUT_INST_FILE_NAME = '{}'\n\n",
220 ),
221 output_verilog_file_name,
222 verilog_path.file_name().unwrap().to_str().unwrap(),
223 verilog_path.file_stem().unwrap().to_str().unwrap(),
224 output_inst_file_name,
225 inst_path.file_name().unwrap().to_str().unwrap(),
226 )?;
227 Ok(())
228 }
229
230 fn update_py_indent_space(&self, line: &str, py_indent_space: usize) -> usize {
231 if !line.is_empty() {
232 let re = regex::Regex::new(r":\s*(#|$)").unwrap();
233 line.chars().position(|c| !c.is_whitespace()).unwrap_or(0)
234 + if re.is_match(line) {
235 self.config.tab_size as usize
236 } else {
237 0usize
238 }
239 } else {
240 py_indent_space
241 }
242 }
243
244 pub fn convert<W: Write>(&self, mut stream: W) -> Result<(), Box<dyn Error>> {
246 let mut first_py_line = false;
247 let mut py_indent_prior = 0usize;
248 let mut py_indent_space = 0usize;
249 let magic_string_len = 2 + self.config.magic_comment_str.len();
250 #[cfg(feature = "inst")]
251 let mut within_inst = false;
252 #[cfg(feature = "inst")]
253 let mut inst_indent_space = 0usize;
254 let mut inst_str = String::new();
255 #[cfg(feature = "inst")]
256 if let Some(vars) = &self.vars {
258 if !vars.is_empty() {
259 writeln!(stream, "# User-defined variables:")?;
260 for (name, value) in vars {
261 writeln!(stream, "{} = {}", name, value)?;
262 }
263 writeln!(stream)?;
264 }
265 }
266 if let Some(preamble_py) = &self.preamble_py {
268 let preamble_py = std::fs::read_to_string(preamble_py)?;
270 writeln!(stream, "# Preamble:\n{}", preamble_py)?;
271 }
272 writeln!(
273 stream,
274 concat!(
275 "# PyTV utility functions:\n",
276 "_inst_file = open('{}', 'w')\n",
277 "def _inst_var_map(tuples):\n",
278 " s = ['%s: %s\\n' % tuple for tuple in tuples]\n",
279 " return ' '.join(s)\n\n",
280 "def _verilog_ports_var_map(tuples, first_port):\n",
281 " s = [' .%s(%s)' % tuple for tuple in tuples]\n",
282 " return ('' if first_port else ',\\n') + ',\\n'.join(s)\n\n",
283 "def _verilog_vparams_var_map(tuples, first_vparam):\n",
284 " s = ['\\n .%s(%s)' % tuple for tuple in tuples]\n",
285 " return ('#(' if first_vparam else ',') + ','.join(s)\n",
286 ),
287 self.output_inst_file_name()
288 )?;
289 #[cfg(feature = "macro")]
290 self.print_macros(&mut stream)?;
291 let mut line_type = LineType::default();
292 for line in self.open_input()?.lines() {
294 let line = self.pre_process_line(&line);
295 self.switch_line_type(&mut line_type, line.as_str());
296 match line_type {
297 LineType::PythonBlock(true) => {
298 py_indent_space = self.update_py_indent_space(&line, py_indent_space);
299 #[cfg(feature = "inst")]
300 self.process_python_line(
301 &line,
302 0,
303 &mut stream,
304 &mut within_inst,
305 &mut inst_str,
306 &mut inst_indent_space,
307 )?;
308 #[cfg(not(feature = "inst"))]
309 self.process_python_line(&line, 0, &mut stream)?;
310 }
311 LineType::PythonInline => {
312 let line = utf8_slice::from(line.trim_start(), magic_string_len);
313 if !first_py_line && !line.is_empty() {
314 first_py_line = true;
315 py_indent_prior =
316 line.chars().position(|c| !c.is_whitespace()).unwrap_or(0);
317 }
318 if !utf8_slice::till(&line, py_indent_prior).trim().is_empty() {
319 Err(format!(
320 "Python line should start with {} spaces.\nUnexpected line: {}",
321 py_indent_prior, &line
322 ))?;
323 }
324 py_indent_space =
325 self.update_py_indent_space(&line, py_indent_space) - py_indent_prior;
326 #[cfg(feature = "inst")]
327 self.process_python_line(
328 &line,
329 py_indent_prior,
330 &mut stream,
331 &mut within_inst,
332 &mut inst_str,
333 &mut inst_indent_space,
334 )?;
335 #[cfg(not(feature = "inst"))]
336 self.process_python_line(&line, py_indent_prior, &mut stream)?;
337 }
338 LineType::Verilog => {
339 let line = self.apply_verilog_regex(self.escape_verilog(&line).as_str());
340 writeln!(stream, "{}print(f'{line}')", " ".repeat(py_indent_space))?;
341 }
342 _ => {}
343 }
344 }
345 #[cfg(feature = "inst")]
346 writeln!(stream, "_inst_file.close()")?;
347 Ok(())
348 }
349
350 pub fn convert_to_file(&self) -> Result<(), Box<dyn Error>> {
354 let out_f = self.open_output()?;
355 self.convert(out_f)?;
356 if self.config.run_python {
357 self.run_python()?;
358 }
359 Ok(())
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366
367 #[test]
368 fn test_pre_process_line() {
369 let convert = Convert::default();
370 dbg!(convert.config.tab_size);
371 assert_eq!(convert.pre_process_line("hello\they"), "hello hey");
372 }
373
374 #[test]
375 fn test_escape_verilog() {
376 let convert = Convert::default();
377 assert_eq!(convert.escape_verilog("hello'world"), "hello\\'world");
378 assert_eq!(convert.escape_verilog("string {foo}"), "string {{foo}}");
379 assert_eq!(
380 convert.escape_verilog("string {{bar}}"),
381 "string {{{{bar}}}}"
382 );
383 assert_eq!(convert.escape_verilog("\"em"), "\"em");
384 }
385
386 #[test]
387 fn test_apply_verilog_regex() {
388 let convert = Convert::default();
389 assert_eq!(
390 convert.apply_verilog_regex("hello `world`"),
391 "hello {world}"
392 );
393 assert_eq!(
394 convert.apply_verilog_regex("hello `world` `bar`"),
395 "hello {world} {bar}"
396 );
397 assert_eq!(
398 convert.apply_verilog_regex("`timescale 1ns / 1ps"),
399 "`timescale 1ns / 1ps"
400 );
401 }
402
403 #[test]
404 fn test_switch_line_type() {
405 let mut line_type = LineType::default();
406 let convert = Convert::default();
407 convert.switch_line_type(&mut line_type, "assign a = b;");
408 assert_eq!(line_type, LineType::Verilog);
409 convert.switch_line_type(&mut line_type, "//! num = 2 ** n;");
410 assert_eq!(line_type, LineType::PythonInline);
411 convert.switch_line_type(&mut line_type, " //! num = num + 1;");
412 assert_eq!(line_type, LineType::PythonInline);
413 convert.switch_line_type(&mut line_type, "/*!");
414 assert_eq!(line_type, LineType::PythonBlock(false));
415 convert.switch_line_type(&mut line_type, "num = 2 ** n;");
416 assert_eq!(line_type, LineType::PythonBlock(true));
417 convert.switch_line_type(&mut line_type, "*/");
418 assert_eq!(line_type, LineType::None);
419 convert.switch_line_type(&mut line_type, "// Verilog comment");
420 assert_eq!(line_type, LineType::Verilog);
421 }
422}