Skip to content

Commit 6713e2a

Browse files
committed
Close #512: MAD-X: Call, FILENAME Support
1 parent f9bdebd commit 6713e2a

2 files changed

Lines changed: 404 additions & 2 deletions

File tree

src/python/impactx/MADXParser.py

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ class MADXInputWarning(UserWarning):
5151
pass
5252

5353

54+
class _ReturnStatement(Exception):
55+
"""Internal signal that a RETURN statement was encountered in a CALL'd file."""
56+
57+
pass
58+
59+
5460
# =============================================================================
5561
# Token Types
5662
# =============================================================================
@@ -961,13 +967,20 @@ def _parse_statement(self):
961967
self._parse_variable_assignment(integer=True)
962968
return
963969

970+
if name == "call":
971+
self._parse_call_command()
972+
return
973+
974+
if name == "return":
975+
# RETURN stops reading the current file (used in CALL'd files)
976+
self._skip_until_semicolon()
977+
raise _ReturnStatement()
978+
964979
if name in (
965980
"title",
966981
"option",
967982
"select",
968983
"twiss",
969-
"call",
970-
"return",
971984
"print",
972985
"value",
973986
"show",
@@ -1290,6 +1303,91 @@ def _parse_use_command(self):
12901303

12911304
self._expect(TokenType.SEMICOLON)
12921305

1306+
def _parse_call_command(self):
1307+
"""
1308+
Parse the CALL command.
1309+
1310+
Supports both MAD-X and MAD-8 syntax:
1311+
CALL, FILE="other_file";
1312+
CALL, FILENAME="other_file";
1313+
1314+
The called file is read and parsed. If the called file contains a
1315+
RETURN statement, parsing stops at that point. Called files may
1316+
themselves contain CALL statements to any depth.
1317+
"""
1318+
self._advance() # skip 'call'
1319+
1320+
filename = None
1321+
1322+
while self._current().type == TokenType.COMMA:
1323+
self._advance() # skip comma
1324+
1325+
if self._current().type != TokenType.IDENTIFIER:
1326+
break
1327+
1328+
attr_name = self._advance().value.lower()
1329+
1330+
if attr_name in ("file", "filename"):
1331+
self._expect(TokenType.EQUALS)
1332+
if self._current().type == TokenType.STRING:
1333+
filename = self._advance().value
1334+
elif self._current().type == TokenType.IDENTIFIER:
1335+
# Some MAD-X files use unquoted filenames
1336+
filename = self._advance().value
1337+
else:
1338+
raise MADXInputError(
1339+
"Expected filename after FILE=", self._current().line
1340+
)
1341+
else:
1342+
# Unknown attribute, skip its value
1343+
if self._current().type == TokenType.EQUALS:
1344+
self._advance()
1345+
self._parse_expression()
1346+
1347+
self._expect(TokenType.SEMICOLON)
1348+
1349+
if filename is None:
1350+
raise MADXInputError("CALL command requires FILE= or FILENAME= attribute")
1351+
1352+
# Resolve the filename relative to the current file's directory
1353+
if self._current_file and self._current_file != "<string>":
1354+
base_dir = os.path.dirname(os.path.abspath(self._current_file))
1355+
resolved = os.path.join(base_dir, filename)
1356+
else:
1357+
resolved = filename
1358+
1359+
if not os.path.isfile(resolved):
1360+
raise FileNotFoundError(
1361+
f"CALL: File '{filename}' not found (resolved to '{resolved}')"
1362+
)
1363+
1364+
# Save parser state
1365+
saved_tokens = self.tokens
1366+
saved_pos = self.pos
1367+
saved_file = self._current_file
1368+
1369+
try:
1370+
# Parse the called file
1371+
self._current_file = resolved
1372+
with open(resolved, "r") as f:
1373+
text = f.read()
1374+
1375+
lexer = MADXLexer(text)
1376+
self.tokens = lexer.tokenize()
1377+
self.pos = 0
1378+
1379+
while self._current().type != TokenType.EOF:
1380+
self._parse_statement()
1381+
self._skip_semicolons()
1382+
except _ReturnStatement:
1383+
# RETURN was encountered in the called file — stop reading it
1384+
pass
1385+
finally:
1386+
# Restore parser state
1387+
self.tokens = saved_tokens
1388+
self.pos = saved_pos
1389+
self._current_file = saved_file
1390+
12931391
# =========================================================================
12941392
# Public Interface (backward compatible with old parser)
12951393
# =========================================================================

0 commit comments

Comments
 (0)