Skip to content

Commit fc6d4e7

Browse files
Fix for repeats and file includes with line numbers, new error directive, dont replace within double quotes, default definitions (__FILE__, etc.)
1 parent d0c51b7 commit fc6d4e7

File tree

6 files changed

+118
-56
lines changed

6 files changed

+118
-56
lines changed

README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Recreation of `vpp` version 2.0.3d which is Copyright (c) 2006-2020 Takashige Su
66

77
## Build and Install
88

9-
From Linux, simply make `src/vhdlproc` executable by running `chmod +x src/vhdlproc`. Then the program can be run directly via `./src/vhdlproc`. Requires the Python libs `os`, `sys`, `random`, and `argparse`.
9+
From Linux, simply make `src/vhdlproc` executable by running `chmod +x src/vhdlproc`. Then the program can be run directly via `./src/vhdlproc`. Requires the Python libs `os`, `sys`, `random`, `datetime`, and `argparse`.
1010

1111
## Usage
1212

@@ -51,10 +51,10 @@ $ cat tests/define.vhdl | ./src/vhdlproc -
5151
the location of the source
5252
5353
`define LABEL - Define LABEL for `ifdef and `ifndef
54-
Will replace even when LABEL attached to an attribute
55-
Will replace even when LABEL is within quotes
5654
5755
`define LABEL STRING - Replace LABEL by STRING, can be multiple words
56+
Will replace ignoring single quotes and attributes ('', ')
57+
Will not replace within double quotes ("")
5858
5959
`rand LABEL FORMAT - Replace LABEL by generated random characters
6060
according to FORMAT. FORMAT has an alphabet
@@ -72,17 +72,19 @@ $ cat tests/define.vhdl | ./src/vhdlproc -
7272
is valid until `else or `endif
7373
Can be nested
7474
75-
`else - Reverse condition for `ifdef and `ifndef (when not
76-
overridden by nested if)
75+
`else - Reverse condition for `ifdef and `ifndef
7776
7877
`endif - Terminator for `ifdef, `ifndef and `else
7978
80-
`for LABEL - Duplicate program code until `endfor LABEL times
79+
`for INT - Duplicate program code until `endfor INT times
8180
Can be nested
8281
8382
`endfor - Terminator for `for
8483
8584
`message STRING - Print STRING to the standard output stream
85+
86+
`error STRING - Print STRING to standard error output stream
87+
Will force close VHDLproc without saving
8688
```
8789

8890
The preprocessor character (default: \` ) can either be changed by the command line option `--directive CHAR` or by the environment variable `VHDLPROC_DIRECTIVE`. The command line option supersedes the environment variable, which supersedes the default.
@@ -91,6 +93,12 @@ It's possible to use `#` as the preprocessor directive by passing in `--directiv
9193

9294
The comment character (default: -- ) can either be changed by the command line option `--comment CHAR` or by the environment variable `VHDLPROC_COMMENT`. The command line option supersedes the environment variable, which supersedes the default.
9395

96+
There are some predefined labels as follows:
97+
- `__FILE__`: Full path to the input file, set to `"STDIN"` when reading from stdin
98+
- `__LINE__`: The current input line number as a string, not affected by for repeats
99+
- `__DATE__`: The current date formatted as a string as `"Jan 01 1970"`
100+
- `__TIME__`: The current time formatted as a string as `"00:00:00"`, 24 hour clock
101+
94102
## Examples
95103

96104
More examples included under `tests/`.

src/vhdlproc

Lines changed: 91 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,18 @@
33
import os, sys
44
import random
55
import string
6+
import datetime as dt
67
import argparse
78

8-
__version__ = "1.1.1"
9+
__version__ = "1.1.2"
910

1011
def randHandler(r):
1112
try:
1213
i = r[0]
1314
x = int(r[1:])
1415
o = ""
1516
except:
16-
print(f"ERROR: Incorrect rand directive (line { iter + 1 })", file=sys.stderr)
17+
print(f"Error: Incorrect rand directive (line { iter + 1 })", file=sys.stderr)
1718
exit(1)
1819

1920
if i == 'B':
@@ -28,15 +29,25 @@ def randHandler(r):
2829
for j in range(x):
2930
o += str(random.choice(string.ascii_letters)).upper()
3031
else:
31-
print(f"ERROR: Incorrect rand directive (line { iter + 1 })", file=sys.stderr)
32+
print(f"Error: Incorrect rand directive (line { iter + 1 })", file=sys.stderr)
3233
exit(1)
3334
return o
3435

3536
def splitLineIntoList(line):
37+
if line is None:
38+
return []
39+
elif line == "":
40+
return [""]
3641
out = []
3742
curr = []
43+
quote = False
44+
quote_ch = False
3845
for char in line:
39-
if char.isalnum() or char == "_":
46+
quote_ch = False
47+
if char == '"' and not quote:
48+
quote = True
49+
quote_ch = True
50+
if char.isalnum() or char == "_" or quote:
4051
curr.append(char)
4152
else:
4253
if curr == []:
@@ -45,6 +56,8 @@ def splitLineIntoList(line):
4556
out.append(curr)
4657
out.append([char])
4758
curr = []
59+
if char == '"' and quote and not quote_ch:
60+
quote = False
4861

4962
if curr != []:
5063
out.append(curr)
@@ -54,6 +67,14 @@ def splitLineIntoList(line):
5467

5568
return out
5669

70+
def definitionSubsitute(words, definitions):
71+
for i in range(len(words)):
72+
word = words[i]
73+
if word in definitions and definitions[word] is not None:
74+
if args.v: print(f"{iter:3}", "Replacing", word, "with", definitions[word])
75+
words[i] = definitions[word]
76+
return ''.join(words)
77+
5778
parser = argparse.ArgumentParser(description=f"VHDLproc v{__version__} - VHDL Preprocessor")
5879
parser.add_argument('input', nargs='?', help='Input file (Pass - to read from stdin)')
5980
parser.add_argument('output', nargs='?', help=' Output file (defaults to [filename]-out.vhdl) (pass - to print to stdout)')
@@ -72,9 +93,8 @@ elif 'VHDLPROC_DIRECTIVE' in os.environ:
7293
DIRECTIVE_CHAR = os.environ['VHDLPROC_DIRECTIVE']
7394
else:
7495
DIRECTIVE_CHAR = "`"
75-
7696
if DIRECTIVE_CHAR == "":
77-
print("ERROR: Empty directive char")
97+
print("Error: Empty directive char", file=sys.stderr)
7898
exit(1)
7999

80100
if args.listppd:
@@ -86,9 +106,10 @@ if args.listppd:
86106
the location of the source
87107
88108
{DIRECTIVE_CHAR}define LABEL - Define LABEL for `ifdef and `ifndef
89-
Will replace even when LABEL attached to an attribute
90109
91110
{DIRECTIVE_CHAR}define LABEL STRING - Replace LABEL by STRING, can be multiple words
111+
Will replace ignoring single quotes and attributes ('', ')
112+
Will not replace within double quotes ("")
92113
93114
{DIRECTIVE_CHAR}rand LABEL FORMAT - Replace LABEL by generated random characters
94115
according to FORMAT. FORMAT has an alphabet
@@ -110,12 +131,15 @@ if args.listppd:
110131
111132
{DIRECTIVE_CHAR}endif - Terminator for `ifdef, `ifndef and `else
112133
113-
{DIRECTIVE_CHAR}for LABEL - Duplicate program code until `endfor LABEL times
134+
{DIRECTIVE_CHAR}for INT - Duplicate program code until `endfor INT times
114135
Can be nested
115136
116137
{DIRECTIVE_CHAR}endfor - Terminator for `for
117138
118139
{DIRECTIVE_CHAR}message STRING - Print STRING to the standard output stream
140+
141+
{DIRECTIVE_CHAR}error STRING - Print STRING to standard error output stream
142+
Will force close VHDLproc without saving
119143
''')
120144
exit(0)
121145
elif args.input == "-":
@@ -173,124 +197,143 @@ forstack = []
173197

174198
definitions = {}
175199

200+
if args.input != "-":
201+
definitions["__FILE__"] = "\"" + os.path.abspath(args.input) + "\""
202+
else:
203+
definitions["__FILE__"] = "\"STDIN\""
204+
definitions["__DATE__"] = "\"" + dt.datetime.now().strftime("%b %d %Y") + "\""
205+
definitions["__TIME__"] = "\"" + dt.datetime.now().strftime("%H:%M:%S") + "\""
206+
176207
if args.define is not None:
177208
for d in args.define:
178209
if '=' in d:
179210
definitions[d.split('=')[0]] = d.split('=')[1]
180211
else:
181212
definitions[d] = None
182-
iter = -1
183-
while(iter < len(intext) - 1):
184-
iter = iter + 1
213+
214+
for iter in range(len(intext)):
185215
line = intext[iter].strip()
216+
definitions["__LINE__"] = str(iter + 1)
217+
218+
if args.v: print(f"{iter:3}", ":", line)
186219

187220
# Check if opening comment in line
188-
if "/*" in line:
221+
if (len(line) > 0 and line.strip()[:len(DIRECTIVE_CHAR)] != DIRECTIVE_CHAR) and "/*" in splitLineIntoList(line):
189222
commentflag = True
190223

191224
if commentflag:
192225
intext[iter] = COMMENT_CHAR + intext[iter]
193226

194227
# Check if closing comment in line
195-
if "*/" in line:
228+
if (len(line) > 0 and line.strip()[:len(DIRECTIVE_CHAR)] != DIRECTIVE_CHAR) and "*/" in splitLineIntoList(line):
196229
commentflag = False
197230
elif commentflag:
198231
continue
199232

200233
if len(line) < 1:
201-
if args.v: print(iter, "Skipping empty line")
234+
if args.v: print(f"{iter:3}", "Skipping empty line")
202235
continue
203236

204237
# Check for preprocessor functions
205238
if DIRECTIVE_CHAR == line[:len(DIRECTIVE_CHAR)]:
206239
instr = line[len(DIRECTIVE_CHAR):].strip().split(' ')
207-
if args.v: print(iter + 1, "Instruction:", ' '.join(instr))
208240
intext[iter] = COMMENT_CHAR + intext[iter]
241+
if args.v: print(f"{iter:3}", "Instruction:", ' '.join(instr))
242+
209243
if instr[0] == 'include':
210-
if args.v: print(iter, "Including", instr[1])
244+
if args.v: print(f"{iter:3}", "Including", instr[1])
211245
inclfile = open(inpath + "/" + instr[1].replace('"',''), 'r')
212-
incltext = inclfile.read()
213-
intext.insert(iter + 1, incltext)
246+
incltext = COMMENT_CHAR + line + '\n' + inclfile.read()
247+
# replace the line instead of inserting to not mess with the number of lines
248+
intext[iter] = incltext
249+
214250
elif instr[0] == 'define':
215251
if len(instr) >= 3:
216-
if args.v: print(iter, "Defining", instr[1], "as", ' '.join(instr[2:]))
252+
if args.v: print(f"{iter:3}", "Defining", instr[1], "as", ' '.join(instr[2:]))
217253
definitions[instr[1]] = ' '.join(instr[2:])
218254
elif len(instr) == 2:
219-
if args.v: print(iter, "Defining", instr[1])
255+
if args.v: print(f"{iter:3}", "Defining", instr[1])
220256
definitions[instr[1]] = None
257+
221258
elif instr[0] == 'rand':
222259
if len(instr) != 3:
223-
print(f"ERROR: Incorrect rand directive (line { iter + 1 })")
260+
print(f"Error: Incorrect rand directive (line { iter + 1 })")
224261
exit(1)
225262
rand = randHandler(instr[2])
226-
if args.v: print(iter, "Defining", instr[1], "as", rand)
263+
if args.v: print(f"{iter:3}", "Defining", instr[1], "as", rand)
227264
definitions[instr[1]] = rand
265+
228266
elif instr[0] == 'undef':
229267
if instr[1] in definitions:
230-
if args.v: print(iter, "Undefining", instr[1])
268+
if args.v: print(f"{iter:3}", "Undefining", instr[1])
231269
definitions.pop(instr[1])
270+
232271
elif instr[0] == 'ifdef':
233-
if args.v: print(iter, "Pushing if stack")
272+
if args.v: print(f"{iter:3}", "Pushing if stack")
234273
if str(instr[1]) in definitions and not ifstack[-1]:
235274
ifstack.append(False)
236275
else:
237276
ifstack.append(True)
277+
238278
elif instr[0] == 'ifndef':
239-
if args.v: print(iter, "Pushing if stack")
279+
if args.v: print(f"{iter:3}", "Pushing if stack")
240280
if str(instr[1]) not in definitions and not ifstack[-1]:
241281
ifstack.append(False)
242282
else:
243283
ifstack.append(True)
284+
244285
elif instr[0] == 'else':
245286
if not ifstack[-2]:
246-
if args.v: print(iter, "Elsing if stack")
287+
if args.v: print(f"{iter:3}", "Elsing if stack")
247288
ifstack[-1] = not ifstack[-1]
289+
248290
elif instr[0] == 'endif':
249-
if args.v: print(iter, "Popping if stack")
291+
if args.v: print(f"{iter:3}", "Popping if stack")
250292
ifstack.pop()
293+
251294
elif instr[0] == 'for':
252-
if args.v: print(iter, "Pushing for stack")
295+
if args.v: print(f"{iter:3}", "Pushing for stack")
253296
forstack.append((iter, int(instr[1])))
254-
pass
297+
255298
elif instr[0] == 'endfor':
256299
incltext = ''
257-
if args.v: print(iter, "Popping for stack")
300+
if args.v: print(f"{iter:3}", "Popping for stack")
258301
pane = forstack.pop()
259302
for j in range(pane[1] - 1):
260-
if j != 0:
261-
incltext = incltext + '\n' + COMMENT_CHAR + '\n' + '\n'.join(intext[pane[0]+1:iter])
262-
else:
263-
incltext = incltext + COMMENT_CHAR + '\n' + '\n'.join(intext[pane[0]+1:iter])
264-
intext.insert(iter, incltext)
265-
pass
303+
incltext = incltext + COMMENT_CHAR + '\n' + '\n'.join(intext[pane[0]+1:iter]) + '\n'
304+
incltext = incltext + COMMENT_CHAR + "`endfor"
305+
# replace the line instead of inserting to not mess with the number of lines
306+
intext[iter] = incltext
307+
266308
elif instr[0] == 'message':
267309
print("Message:", ' '.join(instr[1:]))
310+
311+
elif instr[0] == 'error':
312+
print("Error:", ' '.join(instr[1:]), file=sys.stderr)
313+
exit(1)
314+
268315
else:
269316
print("Unknown directive:", instr[0])
270317

318+
# print if/for stacks if not default
271319
if args.v:
272-
if ifstack != []: print(iter, "ifstack: ", ifstack)
273-
if forstack != []: print(iter, "forstack:", forstack)
320+
if ifstack != [False]: print(f"{iter:3}", "ifstack: ", ifstack)
321+
if forstack != []: print(f"{iter:3}", "forstack:", forstack)
274322

323+
# comment out lines per the if instructions
275324
if ifstack[-1] and intext[iter].strip()[:len(COMMENT_CHAR)] != COMMENT_CHAR:
276325
intext[iter] = COMMENT_CHAR + intext[iter]
277326

278327
# Skip commented and preprocessor lines
279-
if intext[iter].strip()[:len(COMMENT_CHAR)] == COMMENT_CHAR:
280-
if args.v: print(iter, "Skipping commented/preproc line")
328+
if intext[iter].strip()[:len(COMMENT_CHAR)] == COMMENT_CHAR or intext[iter].strip()[:len(DIRECTIVE_CHAR)] == DIRECTIVE_CHAR:
329+
if args.v: print(f"{iter:3}", "Skipping commented/preproc line")
281330
continue
282331

283332
# Replace defined words
284333
words = splitLineIntoList(line)
285-
for i in range(len(words)):
286-
word = words[i]
287-
if word in definitions and definitions[word] is not None:
288-
if args.v: print(iter, "Replacing", word, "with", definitions[word])
289-
words[i] = definitions[word]
290-
291-
intext[iter] = ''.join(words)
334+
intext[iter] = definitionSubsitute(words, definitions)
292335

293-
if args.v: print(iter, "End of file")
336+
if args.v: print(f"{iter:3}", "End of file")
294337

295338
if forstack != []:
296339
print("Error: mismatching for directives", file=sys.stderr)

tests/define.vhdl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
`define TEST "hello"
2-
`define HELLO fun
2+
`define HELLO fun
33

4-
TEST
4+
"TEST __LINE__"
55

6+
__LINE__ __FILE__ __DATE__ __TIME__
67

78
`define TEST2
89

tests/include.vhdl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
`include "include-to.vhdl"
2+
3+
this is a test

tests/message.vhdl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
`message This test was successful
2+
3+
`error this is an error message

tests/replace.vhdl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
`define hello 4
2+
`define there "people"
3+
4+
"hello there"
5+
hello there
6+
'hello there'

0 commit comments

Comments
 (0)