• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

idaholab / MontePy / 21307945836

24 Jan 2026 02:52AM UTC coverage: 98.06% (-0.04%) from 98.095%
21307945836

push

github

MicahGale
Updated Spheres to use args_checked

13 of 13 new or added lines in 3 files covered. (100.0%)

14 existing lines in 5 files now uncovered.

8239 of 8402 relevant lines covered (98.06%)

0.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.13
/montepy/input_parser/input_syntax_reader.py
1
# Copyright 2024-2025, Battelle Energy Alliance, LLC All Rights Reserved.
2
from collections import deque
1✔
3
import itertools
1✔
4
import io
1✔
5
import re
1✔
6
import os
1✔
7
import sly
1✔
8
import warnings
1✔
9

10
from montepy.constants import *
1✔
11
from montepy.exceptions import *
1✔
12
from montepy.input_parser.block_type import BlockType
1✔
13
from montepy.input_parser.input_file import MCNP_InputFile
1✔
14
from montepy.input_parser.mcnp_input import Input, Message, ReadInput, Title
1✔
15
from montepy.input_parser.read_parser import ReadParser
1✔
16
from montepy.utilities import is_comment
1✔
17

18
reading_queue = deque()
1✔
19

20

21
def read_input_syntax(input_file, mcnp_version=DEFAULT_VERSION, replace=True):
1✔
22
    """Creates a generator function to return a new MCNP input for
23
    every new one that is encountered.
24

25
    This is meant to just handle the MCNP input syntax, it does not
26
    semantically parse the inputs.
27

28
    The version must be a three component tuple e.g., (6, 2, 0) and (5, 1, 60).
29

30
    Parameters
31
    ----------
32
    input_file : MCNP_InputFile
33
        the path to the input file to be read
34
    mcnp_version : tuple
35
        The version of MCNP that the input is intended for.
36
    replace : bool
37
        replace all non-ASCII characters with a space (0x20)
38

39
    Returns
40
    -------
41
    generator
42
        a generator of MCNP_Object objects
43
    """
44
    global reading_queue
45
    reading_queue = deque()
1✔
46
    if input_file.is_stream:
1✔
47
        context = input_file
1✔
48
    else:
49
        context = input_file.open("r", replace=replace)
1✔
50
    with context as fh:
1✔
51
        yield from read_front_matters(fh, mcnp_version)
1✔
52
        yield from read_data(fh, mcnp_version)
1✔
53

54

55
def read_front_matters(fh, mcnp_version):
1✔
56
    """Reads the beginning of an MCNP file for all of the unusual data there.
57

58
    This is a generator function that will yield multiple :class:`MCNP_Input` instances.
59

60
    Warnings
61
    --------
62
    This function will move the file handle forward in state.
63

64
    Warnings
65
    --------
66
    This function will not close the file handle.
67

68
    Parameters
69
    ----------
70
    fh : MCNP_InputFile
71
        The file handle of the input file.
72
    mcnp_version : tuple
73
        The version of MCNP that the input is intended for.
74

75
    Returns
76
    -------
77
    MCNP_Object
78
        an instance of the Title class, and possible an instance of a
79
        Message class
80
    """
81
    is_in_message_block = False
1✔
82
    found_title = False
1✔
83
    lines = []
1✔
84
    raw_lines = []
1✔
85
    for i, line in enumerate(fh):
1✔
86
        if i == 0 and line.upper().startswith("MESSAGE:"):
1✔
87
            is_in_message_block = True
1✔
88
            raw_lines.append(line.rstrip())
1✔
89
            lines.append(line[9:])  # removes "MESSAGE: "
1✔
90
        elif is_in_message_block:
1✔
91
            if line.strip():
1✔
92
                raw_lines.append(line.rstrip())
1✔
93
                lines.append(line)
1✔
94
            # message block is terminated by a blank line
95
            else:
96
                yield Message(raw_lines, lines)
1✔
97
                is_in_message_block = False
1✔
98
        # title always follows complete message, or is first
99
        else:
100
            yield Title([line], line)
1✔
101
            break
1✔
102

103

104
def read_data(fh, mcnp_version, block_type=None, recursion=False):
1✔
105
    """Reads the bulk of an MCNP file for all of the MCNP data.
106

107
    This is a generator function that will yield multiple :class:`MCNP_Input` instances.
108

109
    Warnings
110
    --------
111
    This function will move the file handle forward in state.
112

113
    Warnings
114
    --------
115
    This function will not close the file handle.
116

117
    Parameters
118
    ----------
119
    fh : MCNP_InputFile
120
        The file handle of the input file.
121
    mcnp_version : tuple
122
        The version of MCNP that the input is intended for.
123
    block_type : BlockType
124
        The type of block this file is in. This is only used with
125
        partial files read using the ReadInput.
126
    recursion : bool
127
        Whether or not this is being called recursively. If True this
128
        has been called from read_data. This prevents the reading queue
129
        causing infinite recursion.
130

131
    Returns
132
    -------
133
    MCNP_Input
134
        MCNP_Input instances: Inputs that represent the data in the MCNP
135
        input.
136
    """
137
    current_file = fh
1✔
138
    line_length = get_max_line_length(mcnp_version)
1✔
139
    block_counter = 0
1✔
140
    if block_type is None:
1✔
141
        block_type = BlockType.CELL
1✔
142
    continue_input = False
1✔
143
    has_non_comments = False
1✔
144
    input_raw_lines = []
1✔
145

146
    def flush_block():
1✔
147
        nonlocal block_counter, block_type
148
        # keep parsing while there is input or termination has not been triggered
149
        if len(input_raw_lines) > 0:
1✔
150
            yield from flush_input()
1✔
151
        block_counter += 1
1✔
152
        if block_counter < 3:
1✔
153
            block_type = BlockType(block_counter)
1✔
154

155
    def flush_input():
1✔
156
        nonlocal input_raw_lines
157
        # IF 3  BLOCKS are parsed, the rest should be ignored with a warning and print 3 lines
158
        if block_counter >= 3:
1✔
159
            joined_lines = "\n".join(input_raw_lines[0:3])
1✔
160
            msg = f"Unexpected input after line {current_file.lineno - 1}\n line content: {joined_lines}\n"
1✔
161
            warnings.warn(
1✔
162
                msg,
163
                UndefinedBlock,
164
                stacklevel=6,
165
            )
166
            return
1✔
167

168
        start_line = current_file.lineno + 1 - len(input_raw_lines)
1✔
169
        input = Input(
1✔
170
            input_raw_lines,
171
            block_type,
172
            current_file,
173
            start_line,
174
        )
175
        try:
1✔
176
            read_input = ReadInput(
1✔
177
                input_raw_lines, block_type, current_file, start_line
178
            )
179
            reading_queue.append((block_type, read_input.file_name, current_file.path))
1✔
180
            yield None
1✔
181
        except ValueError as e:
1✔
182
            if isinstance(e, ParsingError):
1✔
UNCOV
183
                raise e
×
184
            yield input
1✔
185
        continue_input = False
1✔
186
        input_raw_lines = []
1✔
187

188
    for line in fh:
1✔
189
        line = line.expandtabs(TABSIZE)
1✔
190
        line_is_comment = is_comment(line)
1✔
191
        # transition to next block with blank line
192
        if not line.strip():
1✔
193
            yield from flush_block()
1✔
194
            has_non_comments = False
1✔
195
            continue
1✔
196
        # if a new input
197
        if (
1✔
198
            line[0:BLANK_SPACE_CONTINUE].strip()
199
            and not continue_input
200
            and not line_is_comment
201
            and has_non_comments
202
            and input_raw_lines
203
        ):
204
            yield from flush_input()
1✔
205

206
        # die if it is a vertical syntax format
207
        start_o_line = line[0:BLANK_SPACE_CONTINUE]
1✔
208
        # eliminate comments, and inputs that use # for other syntax
209
        if (
1✔
210
            "#" in start_o_line
211
            and not line_is_comment
212
            and start_o_line.strip().startswith("#")
213
        ):
214
            input_raw_lines.append(line.rstrip())
1✔
215
            input = next(flush_input())
1✔
216
            lineno = 1
1✔
217
            token = sly.lex.Token()
1✔
218
            token.value = "#"
1✔
219
            index = line[0:BLANK_SPACE_CONTINUE].index("#")
1✔
220
            err = {"message": "", "token": token, "line": lineno, "index": index}
1✔
221
            raise UnsupportedFeature(
1✔
222
                "Vertical Input encountered, which is not supported by Montepy",
223
                input,
224
                [err],
225
            )
226
        # cut line down to allowed length
227
        old_line = line
1✔
228
        line = line[:line_length]
1✔
229
        if len(old_line) != len(line):
1✔
230
            comment_free = old_line.split("$")[0]
1✔
231
            if len(comment_free.rstrip()) > line_length and not COMMENT_FINDER.match(
1✔
232
                line
233
            ):
234
                warnings.warn(
1✔
235
                    f"The line number {fh.lineno} exceeded the allowed line length of: {line_length} for MCNP{mcnp_version} "
236
                    f'and "{comment_free[line_length -1:].rstrip()}" was removed.',
237
                    LineOverRunWarning,
238
                )
239
            # if extra length is a comment keep it long
240
            else:
241
                line = old_line
1✔
242
        if line.endswith(" &\n"):
1✔
243
            continue_input = True
1✔
244
        else:
245
            continue_input = False
1✔
246
        has_non_comments = has_non_comments or not line_is_comment
1✔
247
        input_raw_lines.append(line.rstrip())
1✔
248
    yield from flush_block()
1✔
249

250
    if not recursion:
1✔
251
        path = os.path.dirname(fh.name)
1✔
252
        while reading_queue:
1✔
253
            block_type, file_name, parent = reading_queue.popleft()
1✔
254
            new_wrapper = MCNP_InputFile(os.path.join(path, file_name), parent)
1✔
255
            with new_wrapper.open("r") as sub_fh:
1✔
256
                new_wrapper = MCNP_InputFile(file_name, parent)
1✔
257
                for input in read_data(sub_fh, mcnp_version, block_type, True):
1✔
258
                    yield input
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc