Skip to content

Commit 22ad445

Browse files
authored
Merge pull request #17 from dobios/betterparser
Better Parsing
2 parents a89484c + 2ba6d13 commit 22ad445

6 files changed

Lines changed: 1092 additions & 892 deletions

File tree

src/btoropt/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
##########################################################################
22
# BTOR2 parser, code optimizer, and circuit miter
3-
# Copyright (C) 2024 Amelia Dobis
3+
# Copyright (C) 2024-2025 Amelia Dobis
44
#
55
# This program is free software: you can redistribute it and/or modify
66
# it under the terms of the GNU General Public License as published by

src/btoropt/__main__.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
##########################################################################
22
# BTOR2 parser, code optimizer, and circuit miter
3-
# Copyright (C) 2024 Amelia Dobis
3+
# Copyright (C) 2024-2025 Amelia Dobis
44
#
55
# This program is free software: you can redistribute it and/or modify
66
# it under the terms of the GNU General Public License as published by
@@ -19,27 +19,40 @@
1919
from .program import *
2020
from .passes.allpasses import *
2121
from .parser import *
22+
from .modparser import *
23+
from enum import Enum
2224
import sys
2325

24-
options = ["modular"]
26+
MOD = 'modular'
27+
DEF = 'def'
28+
options = [MOD, DEF]
29+
MODE_TYPE = Enum('Mode', [('Seq', 0), ('Modular', 1), ('Def', 2)])
2530

2631
def main():
2732
# Retrieve flags
2833
if len(sys.argv) < 3:
29-
print("Usage: btoropt [optional](--modular) <file.btor2> <pass_names_in_order> ...")
34+
# print(f"Usage: btoropt [optional](--{MOD}, --{DEF}) <file.btor2> <pass_names_in_order> ...")
35+
print(f"Usage: btoropt [optional](--{DEF}) <file.btor2> <pass_names_in_order> ...")
3036
exit(1)
3137

3238
# Check options
3339
base = 1
34-
modular = False
40+
mode = MODE_TYPE.Seq
3541

3642
# NOTE: This if should be a while if you want to introduce more than one option
3743
if "--" in sys.argv[base].strip():
3844
option = sys.argv[base].strip().strip("--")
3945
if option not in options:
4046
print(f"Invalid option given: {option}")
4147
exit(1)
42-
modular = True
48+
else:
49+
# mode = \
50+
# MODE_TYPE.Modular if option == MOD else \
51+
# MODE_TYPE.Def if option == DEF else \
52+
# MODE_TYPE.Seq
53+
mode = \
54+
MODE_TYPE.Def if option == DEF else \
55+
MODE_TYPE.Seq
4356
base += 1
4457

4558

@@ -50,12 +63,17 @@ def main():
5063

5164
# Parse the design
5265
btor2 = None
53-
if modular:
54-
btor2 = parse_file(btor2str)
66+
if mode == MODE_TYPE.Modular:
67+
parser = ModParser(btor2str)
68+
btor2 = parser.parse_file(btor2str)
5569
else:
56-
btor2 = parse(btor2str)
70+
parser = Parser(btor2str)
71+
if mode == MODE_TYPE.Def:
72+
btor2 = parser.parseDeferred()
73+
else:
74+
btor2 = parser.parseSeq()
5775

58-
assert btor2 is not None
76+
assert btor2 is not None, "Parsing failed."
5977

6078
# Fetch the pass names
6179
pass_base = base + 1
@@ -72,13 +90,13 @@ def main():
7290

7391
# Run all passes in the pipeline
7492
for p in pipeline:
75-
if modular:
93+
if mode == MODE_TYPE.Modular:
7694
btor2 = p.runOnProgram(btor2)
7795
else:
7896
btor2 = p.run(btor2)
7997

8098
# Show the result to the user
81-
if(modular):
99+
if(mode == MODE_TYPE.Modular):
82100
print(serialize_p(btor2))
83101
else:
84102
print("Success")

src/btoropt/modparser.py

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
##########################################################################
2+
# BTOR2 parser, code optimizer, and circuit miter
3+
# Copyright (C) 2024-2025 Amelia Dobis
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
##########################################################################
18+
19+
from .program import *
20+
from .parser import *
21+
from tqdm import tqdm
22+
23+
# Old API for module parsing
24+
def parse_file(file: list[str]) -> Program:
25+
parser = ModParser(file)
26+
return parser.parse_file()
27+
28+
# Special parser that handles custom instructions
29+
class ModParser(Parser):
30+
def __init__(self, p_str):
31+
super().__init__(p_str)
32+
self.modules: list[Module] = []
33+
self.contracts: list[Contract] = []
34+
35+
# Checks thaa a given module name has been defined
36+
def check_name(self, name: str) -> bool:
37+
return name in [m.name for m in self.modules]
38+
39+
# Retrives a module by name from a list of parsed modules
40+
def get_module(self, name: str) -> Module:
41+
return [m for m in self.modules if m.name == name][0]
42+
43+
# Extracts a body from an arbitrary code sequence
44+
# Returns the line idx at which the scanning ended
45+
def scan_body(self, i: int) -> tuple[list[str], int]:
46+
res = []
47+
l = self.p_str[i].split(" ")
48+
## Check that the declaration line ends with an '{'
49+
assert str(l[len(l)-1].strip()) == '{', f"invalid body start: {l[len(l)-1]}"
50+
51+
i += 1
52+
while self.p_str[i].strip() != "}":
53+
# Check that there are no nested structures
54+
lid = self.p_str[i].strip().split(" ")[0]
55+
assert lid.isnumeric(), f"All body lines must be instructions! Found: {lid}"
56+
res.append(self.p_str[i].strip())
57+
i += 1
58+
return (res, i)
59+
60+
# Parses a ref instruction (only custom inst that is allowed in both modules and contracts)
61+
# @param inst: the pre-split ref instruction to be parsed
62+
# @param modules: the list of already parsed modules that can be referenced
63+
def parse_ref(self, inst: list[str]) -> Ref:
64+
## Sanity check: Must be a ref instruction
65+
assert inst[1] == "ref", f"`parse_ref` can only handle ref instructions, not {inst[1]}!"
66+
ref_mod = inst[2]
67+
68+
## Sanity check: check that the name exists
69+
assert self.check_name(ref_mod), f"Named module {ref_mod} is undefined!"
70+
71+
module = self.get_module(ref_mod)
72+
val = get_inst(module.body, int(inst[3]))
73+
return Ref(int(inst[0]), ref_mod, val)
74+
75+
# Parse a module's pre-scanned body
76+
# @param body: the list of instructions contained within the body
77+
# @param modules: the list of already parsed modules that can be referenced
78+
def parse_module_body(self, body: list[str]) -> list[Instruction]:
79+
bodyParser = Parser(body)
80+
81+
for line in body:
82+
inst = line.split(" ")
83+
if inst[0] == ";": # handle comments
84+
continue
85+
lid = int(inst[0])
86+
tag = inst[1]
87+
match tag:
88+
# Handle special instructions
89+
case "inst":
90+
instd_mod = inst[2]
91+
## Sanity check: check that the name exists
92+
assert self.check_name(instd_mod), f"Named module {instd_mod} is undefined!"
93+
bodyParser.p.append(Instance(lid, instd_mod))
94+
95+
case "ref":
96+
bodyParser.p.append(self.parse_ref(inst))
97+
98+
case "set":
99+
instance = bodyParser.find_inst(int(inst[2]))
100+
ref = bodyParser.find_inst(int(inst[3]))
101+
assert ref.name == instance.name, "`set` can only set a reference to an instance input!"
102+
alias = bodyParser.find_inst(int(inst[4]))
103+
bodyParser.p.append(Set(lid, instance, ref, alias))
104+
105+
# Handle standard instructions
106+
case _:
107+
bodyParser.parse_inst(line)
108+
109+
return bodyParser.p
110+
111+
# Parse a contract's pre-scanned body
112+
# @param name: the name given to the module
113+
# @param body: the list of instructions contained within the body
114+
# @param modules: the list of already parsed modules that can be referenced
115+
def parse_contract_body(self, body: list[str]) -> list[Instruction]:
116+
bodyParser = Parser(body)
117+
for line in body:
118+
inst = line.split(" ")
119+
if inst[0] == ";": # handle comments
120+
continue
121+
lid = int(inst[0])
122+
tag = inst[1]
123+
match tag:
124+
# Handle special instructions
125+
case "prec":
126+
# Find the op associated to this instruction
127+
cond = bodyParser.find_inst(int(inst[2]))
128+
bodyParser.p.append(Prec(lid, cond))
129+
130+
case "post":
131+
# Find the op associated to this instruction
132+
cond = bodyParser.find_inst(int(inst[2]))
133+
bodyParser.p.append(Post(lid, cond))
134+
135+
case "ref":
136+
bodyParser.p.append(self.parse_ref(inst))
137+
138+
# Handle standard instructions
139+
case _:
140+
bodyParser.parse_inst(line)
141+
142+
return bodyParser.p
143+
144+
# Parse an entire file that can contain contracts and modules
145+
def parse_file(self) -> Program:
146+
i = 0
147+
while i < len(self.p_str):
148+
symbols = self.p_str[i].strip().split(" ")
149+
# Check whether it's a module or a contract
150+
tag = symbols[0]
151+
match tag:
152+
case "module":
153+
name = symbols[1]
154+
# Scan and parse the body
155+
(body, i) = self.scan_body(i)
156+
b = self.parse_module_body(body)
157+
# Create and store the module
158+
self.modules.append(Module(name, b))
159+
160+
case "contract":
161+
name = symbols[1]
162+
assert self.check_name(name), f"Contract name {name} is not defined!"
163+
(body, i) = self.scan_body(i)
164+
body = self.parse_contract_body(body)
165+
# Create and store the module
166+
self.contracts.append(Contract(name, body))
167+
168+
case "}":
169+
i+=1
170+
continue
171+
172+
case _:
173+
print(f"Unsupported structure: {tag} is not module | contract")
174+
exit(1)
175+
176+
return Program(self.modules, self.contracts)

0 commit comments

Comments
 (0)