1 # The MIT License (MIT)
3 # Copyright (c) 2023 Philippe Proulx <eeppeliteloop@gmail.com>
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be
14 # included in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 # This module is the portable Normand processor. It offers both the
25 # parse() function and the command-line tool (run the module itself)
26 # without external dependencies except a `typing` module for Python 3.4.
28 # Feel free to copy this module file to your own project to use Normand.
30 # Upstream repository: <https://github.com/efficios/normand>.
32 __author__
= "Philippe Proulx"
33 __version__
= "0.18.0"
56 from typing
import Any
, Set
, Dict
, List
, Union
, Pattern
, Callable
, NoReturn
, Optional
59 # Text location (line and column numbers).
62 def _create(cls
, line_no
: int, col_no
: int):
63 self
= cls
.__new
__(cls
)
64 self
._init
(line_no
, col_no
)
67 def __init__(*args
, **kwargs
): # type: ignore
68 raise NotImplementedError
70 def _init(self
, line_no
: int, col_no
: int):
71 self
._line
_no
= line_no
85 return "TextLocation({}, {})".format(self
._line
_no
, self
._col
_no
)
90 def __init__(self
, text_loc
: TextLocation
):
91 self
._text
_loc
= text_loc
93 # Source text location.
100 class _ScalarItem(_Item
):
101 # Returns the size, in bytes, of this item.
104 def size(self
) -> int:
114 class _Byte(_ScalarItem
, _RepableItem
):
115 def __init__(self
, val
: int, text_loc
: TextLocation
):
116 super().__init
__(text_loc
)
129 return "_Byte({}, {})".format(hex(self
._val
), repr(self
._text
_loc
))
133 class _Str(_ScalarItem
, _RepableItem
):
134 def __init__(self
, data
: bytes
, text_loc
: TextLocation
):
135 super().__init
__(text_loc
)
145 return len(self
._data
)
148 return "_Str({}, {})".format(repr(self
._data
), repr(self
._text
_loc
))
153 class ByteOrder(enum
.Enum
):
161 # Byte order setting.
163 def __init__(self
, bo
: ByteOrder
, text_loc
: TextLocation
):
164 super().__init
__(text_loc
)
172 return "_SetBo({}, {})".format(repr(self
._bo
), repr(self
._text
_loc
))
177 def __init__(self
, name
: str, text_loc
: TextLocation
):
178 super().__init
__(text_loc
)
187 return "_Label({}, {})".format(repr(self
._name
), repr(self
._text
_loc
))
191 class _SetOffset(_Item
):
192 def __init__(self
, val
: int, text_loc
: TextLocation
):
193 super().__init
__(text_loc
)
196 # Offset value (bytes).
202 return "_SetOffset({}, {})".format(repr(self
._val
), repr(self
._text
_loc
))
206 class _AlignOffset(_Item
):
207 def __init__(self
, val
: int, pad_val
: int, text_loc
: TextLocation
):
208 super().__init
__(text_loc
)
210 self
._pad
_val
= pad_val
212 # Alignment value (bits).
217 # Padding byte value.
223 return "_AlignOffset({}, {}, {})".format(
224 repr(self
._val
), repr(self
._pad
_val
), repr(self
._text
_loc
)
228 # Mixin of containing an AST expression and its string.
230 def __init__(self
, expr_str
: str, expr
: ast
.Expression
):
231 self
._expr
_str
= expr_str
237 return self
._expr
_str
239 # Expression node to evaluate.
245 # Fill until some offset.
246 class _FillUntil(_Item
, _ExprMixin
):
248 self
, expr_str
: str, expr
: ast
.Expression
, pad_val
: int, text_loc
: TextLocation
250 super().__init
__(text_loc
)
251 _ExprMixin
.__init
__(self
, expr_str
, expr
)
252 self
._pad
_val
= pad_val
254 # Padding byte value.
260 return "_FillUntil({}, {}, {}, {})".format(
261 repr(self
._expr
_str
),
264 repr(self
._text
_loc
),
268 # Variable assignment.
269 class _VarAssign(_Item
, _ExprMixin
):
271 self
, name
: str, expr_str
: str, expr
: ast
.Expression
, text_loc
: TextLocation
273 super().__init
__(text_loc
)
274 _ExprMixin
.__init
__(self
, expr_str
, expr
)
283 return "_VarAssign({}, {}, {}, {})".format(
285 repr(self
._expr
_str
),
287 repr(self
._text
_loc
),
291 # Fixed-length number, possibly needing more than one byte.
292 class _FlNum(_ScalarItem
, _RepableItem
, _ExprMixin
):
294 self
, expr_str
: str, expr
: ast
.Expression
, len: int, text_loc
: TextLocation
296 super().__init
__(text_loc
)
297 _ExprMixin
.__init
__(self
, expr_str
, expr
)
307 return self
._len
// 8
310 return "_FlNum({}, {}, {}, {})".format(
311 repr(self
._expr
_str
),
314 repr(self
._text
_loc
),
319 class _Leb128Int(_Item
, _RepableItem
, _ExprMixin
):
320 def __init__(self
, expr_str
: str, expr
: ast
.Expression
, text_loc
: TextLocation
):
321 super().__init
__(text_loc
)
322 _ExprMixin
.__init
__(self
, expr_str
, expr
)
325 return "{}({}, {}, {})".format(
326 self
.__class
__.__name
__,
327 repr(self
._expr
_str
),
329 repr(self
._text
_loc
),
333 # Unsigned LEB128 integer.
334 class _ULeb128Int(_Leb128Int
, _RepableItem
, _ExprMixin
):
338 # Signed LEB128 integer.
339 class _SLeb128Int(_Leb128Int
, _RepableItem
, _ExprMixin
):
344 class _Group(_Item
, _RepableItem
):
345 def __init__(self
, items
: List
[_Item
], text_loc
: TextLocation
):
346 super().__init
__(text_loc
)
355 return "_Group({}, {})".format(repr(self
._items
), repr(self
._text
_loc
))
359 class _Rep(_Item
, _ExprMixin
):
361 self
, item
: _Item
, expr_str
: str, expr
: ast
.Expression
, text_loc
: TextLocation
363 super().__init
__(text_loc
)
364 _ExprMixin
.__init
__(self
, expr_str
, expr
)
373 return "_Rep({}, {}, {}, {})".format(
375 repr(self
._expr
_str
),
377 repr(self
._text
_loc
),
382 class _Cond(_Item
, _ExprMixin
):
388 expr
: ast
.Expression
,
389 text_loc
: TextLocation
,
391 super().__init
__(text_loc
)
392 _ExprMixin
.__init
__(self
, expr_str
, expr
)
393 self
._true
_item
= true_item
394 self
._false
_item
= false_item
396 # Item when condition is true.
399 return self
._true
_item
401 # Item when condition is false.
403 def false_item(self
):
404 return self
._false
_item
407 return "_Cond({}, {}, {}, {}, {})".format(
408 repr(self
._true
_item
),
409 repr(self
._false
_item
),
410 repr(self
._expr
_str
),
412 repr(self
._text
_loc
),
416 # Macro definition item.
417 class _MacroDef(_Item
):
419 self
, name
: str, param_names
: List
[str], group
: _Group
, text_loc
: TextLocation
421 super().__init
__(text_loc
)
423 self
._param
_names
= param_names
433 def param_names(self
):
434 return self
._param
_names
442 return "_MacroDef({}, {}, {}, {})".format(
444 repr(self
._param
_names
),
446 repr(self
._text
_loc
),
450 # Macro expansion parameter.
451 class _MacroExpParam
:
452 def __init__(self
, expr_str
: str, expr
: ast
.Expression
, text_loc
: TextLocation
):
453 self
._expr
_str
= expr_str
455 self
._text
_loc
= text_loc
460 return self
._expr
_str
467 # Source text location.
470 return self
._text
_loc
473 return "_MacroExpParam({}, {}, {})".format(
474 repr(self
._expr
_str
), repr(self
._expr
), repr(self
._text
_loc
)
478 # Macro expansion item.
479 class _MacroExp(_Item
, _RepableItem
):
483 params
: List
[_MacroExpParam
],
484 text_loc
: TextLocation
,
486 super().__init
__(text_loc
)
488 self
._params
= params
501 return "_MacroExp({}, {}, {})".format(
504 repr(self
._text
_loc
),
508 # A parsing error message: a string and a text location.
509 class ParseErrorMessage
:
511 def _create(cls
, text
: str, text_loc
: TextLocation
):
512 self
= cls
.__new
__(cls
)
513 self
._init
(text
, text_loc
)
516 def __init__(self
, *args
, **kwargs
): # type: ignore
517 raise NotImplementedError
519 def _init(self
, text
: str, text_loc
: TextLocation
):
521 self
._text
_loc
= text_loc
528 # Source text location.
530 def text_location(self
):
531 return self
._text
_loc
534 # A parsing error containing one or more messages (`ParseErrorMessage`).
535 class ParseError(RuntimeError):
537 def _create(cls
, msg
: str, text_loc
: TextLocation
):
538 self
= cls
.__new
__(cls
)
539 self
._init
(msg
, text_loc
)
542 def __init__(self
, *args
, **kwargs
): # type: ignore
543 raise NotImplementedError
545 def _init(self
, msg
: str, text_loc
: TextLocation
):
546 super().__init
__(msg
)
547 self
._msgs
= [] # type: List[ParseErrorMessage]
548 self
._add
_msg
(msg
, text_loc
)
550 def _add_msg(self
, msg
: str, text_loc
: TextLocation
):
552 ParseErrorMessage
._create
( # pyright: ignore[reportPrivateUsage]
557 # Parsing error messages.
559 # The first message is the most specific one.
565 # Raises a parsing error, forwarding the parameters to the constructor.
566 def _raise_error(msg
: str, text_loc
: TextLocation
) -> NoReturn
:
567 raise ParseError
._create
(msg
, text_loc
) # pyright: ignore[reportPrivateUsage]
570 # Adds a message to the parsing error `exc`.
571 def _add_error_msg(exc
: ParseError
, msg
: str, text_loc
: TextLocation
):
572 exc
._add
_msg
(msg
, text_loc
) # pyright: ignore[reportPrivateUsage]
575 # Appends a message to the parsing error `exc` and reraises it.
576 def _augment_error(exc
: ParseError
, msg
: str, text_loc
: TextLocation
) -> NoReturn
:
577 _add_error_msg(exc
, msg
, text_loc
)
581 # Returns a normalized version (so as to be parseable by int()) of
582 # the constant integer string `s`, possibly negative, dealing with
584 def _norm_const_int(s
: str):
588 if s
.startswith("-"):
593 if pos
.startswith("0" + r
):
609 for suf
in asm_suf_base
:
611 s
= "{}0{}{}".format(neg
, asm_suf_base
[suf
], pos
.rstrip(suf
))
616 # Variables dictionary type (for type hints).
617 VariablesT
= Dict
[str, Union
[int, float]]
620 # Labels dictionary type (for type hints).
621 LabelsT
= Dict
[str, int]
625 _py_name_pat
= re
.compile(r
"[a-zA-Z_][a-zA-Z0-9_]*")
626 _pos_const_int_pat
= re
.compile(
627 r
"(?:0[Xx][A-Fa-f0-9]+|0[Oo][0-7]+|0[Bb][01]+|[A-Fa-f0-9]+[hH]|[0-7]+[qQoO]|[01]+[bB]|\d+)\b"
629 _const_int_pat
= re
.compile(r
"(?P<neg>-)?(?:{})".format(_pos_const_int_pat
.pattern
))
630 _const_float_pat
= re
.compile(
631 r
"[-+]?(?:(?:\d*\.\d+)|(?:\d+\.))(?:[Ee][+-]?\d+)?(?=\W|)"
635 # Macro definition dictionary.
636 _MacroDefsT
= Dict
[str, _MacroDef
]
641 # The constructor accepts a Normand input. After building, use the `res`
642 # property to get the resulting main group.
644 # Builds a parser to parse the Normand input `normand`, parsing
646 def __init__(self
, normand
: str, variables
: VariablesT
, labels
: LabelsT
):
647 self
._normand
= normand
651 self
._label
_names
= set(labels
.keys())
652 self
._var
_names
= set(variables
.keys())
653 self
._macro
_defs
= {} # type: _MacroDefsT
656 # Result (main group).
663 def macro_defs(self
):
664 return self
._macro
_defs
666 # Current text location.
669 return TextLocation
._create
( # pyright: ignore[reportPrivateUsage]
670 self
._line
_no
, self
._col
_no
673 # Returns `True` if this parser is done parsing.
675 return self
._at
== len(self
._normand
)
677 # Returns `True` if this parser isn't done parsing.
678 def _isnt_done(self
):
679 return not self
._is
_done
()
681 # Raises a parse error, creating it using the message `msg` and the
682 # current text location.
683 def _raise_error(self
, msg
: str) -> NoReturn
:
684 _raise_error(msg
, self
._text
_loc
)
686 # Tries to make the pattern `pat` match the current substring,
687 # returning the match object and updating `self._at`,
688 # `self._line_no`, and `self._col_no` on success.
689 def _try_parse_pat(self
, pat
: Pattern
[str]):
690 m
= pat
.match(self
._normand
, self
._at
)
695 # Skip matched string
696 self
._at
+= len(m
.group(0))
699 self
._line
_no
+= m
.group(0).count("\n")
701 # Update column number
702 for i
in reversed(range(self
._at
)):
703 if self
._normand
[i
] == "\n" or i
== 0:
705 self
._col
_no
= self
._at
+ 1
707 self
._col
_no
= self
._at
- i
711 # Return match object
714 # Expects the pattern `pat` to match the current substring,
715 # returning the match object and updating `self._at`,
716 # `self._line_no`, and `self._col_no` on success, or raising a parse
717 # error with the message `error_msg` on error.
718 def _expect_pat(self
, pat
: Pattern
[str], error_msg
: str):
720 m
= self
._try
_parse
_pat
(pat
)
724 self
._raise
_error
(error_msg
)
726 # Return match object
729 # Pattern for _skip_ws_and_comments()
730 _ws_or_syms_or_comments_pat
= re
.compile(
731 r
"(?:[\s/\\?&:;.,[\]_=|-]|#[^#]*?(?:\n|#))*"
734 # Skips as many whitespaces, insignificant symbol characters, and
735 # comments as possible.
736 def _skip_ws_and_comments(self
):
737 self
._try
_parse
_pat
(self
._ws
_or
_syms
_or
_comments
_pat
)
739 # Pattern for _skip_ws()
740 _ws_pat
= re
.compile(r
"\s*")
742 # Skips as many whitespaces as possible.
744 self
._try
_parse
_pat
(self
._ws
_pat
)
746 # Pattern for _try_parse_hex_byte()
747 _nibble_pat
= re
.compile(r
"[A-Fa-f0-9]")
749 # Tries to parse a hexadecimal byte, returning a byte item on
751 def _try_parse_hex_byte(self
):
752 begin_text_loc
= self
._text
_loc
754 # Match initial nibble
755 m_high
= self
._try
_parse
_pat
(self
._nibble
_pat
)
761 # Expect another nibble
762 self
._skip
_ws
_and
_comments
()
763 m_low
= self
._expect
_pat
(
764 self
._nibble
_pat
, "Expecting another hexadecimal nibble"
768 return _Byte(int(m_high
.group(0) + m_low
.group(0), 16), begin_text_loc
)
770 # Patterns for _try_parse_bin_byte()
771 _bin_byte_bit_pat
= re
.compile(r
"[01]")
772 _bin_byte_prefix_pat
= re
.compile(r
"%+")
774 # Tries to parse a binary byte, returning a byte item on success.
775 def _try_parse_bin_byte(self
):
776 begin_text_loc
= self
._text
_loc
779 m
= self
._try
_parse
_pat
(self
._bin
_byte
_prefix
_pat
)
785 # Expect as many bytes as there are `%` prefixes
786 items
= [] # type: List[_Item]
788 for _
in range(len(m
.group(0))):
789 self
._skip
_ws
_and
_comments
()
790 byte_text_loc
= self
._text
_loc
791 bits
= [] # type: List[str]
795 self
._skip
_ws
_and
_comments
()
796 m
= self
._expect
_pat
(
797 self
._bin
_byte
_bit
_pat
, "Expecting a bit (`0` or `1`)"
799 bits
.append(m
.group(0))
801 items
.append(_Byte(int("".join(bits
), 2), byte_text_loc
))
808 return _Group(items
, begin_text_loc
)
810 # Patterns for _try_parse_dec_byte()
811 _dec_byte_prefix_pat
= re
.compile(r
"\$")
812 _dec_byte_val_pat
= re
.compile(r
"(?P<neg>-?)(?P<val>\d+)")
814 # Tries to parse a decimal byte, returning a byte item on success.
815 def _try_parse_dec_byte(self
):
816 begin_text_loc
= self
._text
_loc
819 if self
._try
_parse
_pat
(self
._dec
_byte
_prefix
_pat
) is None:
825 m
= self
._expect
_pat
(self
._dec
_byte
_val
_pat
, "Expecting a decimal constant")
828 val
= int(m
.group("val")) * (-1 if m
.group("neg") == "-" else 1)
831 if val
< -128 or val
> 255:
832 _raise_error("Invalid decimal byte value {}".format(val
), begin_text_loc
)
838 return _Byte(val
, begin_text_loc
)
840 # Tries to parse a byte, returning a byte item on success.
841 def _try_parse_byte(self
):
843 item
= self
._try
_parse
_hex
_byte
()
849 item
= self
._try
_parse
_bin
_byte
()
855 item
= self
._try
_parse
_dec
_byte
()
860 # Patterns for _try_parse_str()
861 _str_prefix_pat
= re
.compile(r
'(?:u(?P<len>16|32)(?P<bo>be|le))?\s*"')
862 _str_suffix_pat
= re
.compile(r
'"')
863 _str_str_pat
= re
.compile(r
'(?:(?:\\.)|[^"])*')
865 # Strings corresponding to escape sequence characters
866 _str_escape_seq_strs
= {
880 # Tries to parse a string, returning a string item on success.
881 def _try_parse_str(self
):
882 begin_text_loc
= self
._text
_loc
885 m
= self
._try
_parse
_pat
(self
._str
_prefix
_pat
)
894 if m
.group("len") is not None:
895 encoding
= "utf_{}_{}".format(m
.group("len"), m
.group("bo"))
898 m
= self
._expect
_pat
(self
._str
_str
_pat
, "Expecting a literal string")
900 # Expect end of string
901 self
._expect
_pat
(self
._str
_suffix
_pat
, 'Expecting `"` (end of literal string)')
903 # Replace escape sequences
906 for ec
in '0abefnrtv"\\':
907 val
= val
.replace(r
"\{}".format(ec
), self
._str
_escape
_seq
_strs
[ec
])
910 data
= val
.encode(encoding
)
913 return _Str(data
, begin_text_loc
)
915 # Common right parenthesis pattern
916 _right_paren_pat
= re
.compile(r
"\)")
918 # Patterns for _try_parse_group()
919 _group_prefix_pat
= re
.compile(r
"\(|!g(?:roup)?\b")
921 # Tries to parse a group, returning a group item on success.
922 def _try_parse_group(self
):
923 begin_text_loc
= self
._text
_loc
926 m_open
= self
._try
_parse
_pat
(self
._group
_prefix
_pat
)
933 items
= self
._parse
_items
()
935 # Expect end of group
936 self
._skip
_ws
_and
_comments
()
938 if m_open
.group(0) == "(":
939 pat
= self
._right
_paren
_pat
942 pat
= self
._block
_end
_pat
945 self
._expect
_pat
(pat
, "Expecting an item or `{}` (end of group)".format(exp
))
948 return _Group(items
, begin_text_loc
)
950 # Returns a stripped expression string and an AST expression node
951 # from the expression string `expr_str` at text location `text_loc`.
952 def _ast_expr_from_str(self
, expr_str
: str, text_loc
: TextLocation
):
953 # Create an expression node from the expression string
954 expr_str
= expr_str
.strip().replace("\n", " ")
957 expr
= ast
.parse(expr_str
, mode
="eval")
960 "Invalid expression `{}`: invalid syntax".format(expr_str
),
964 return expr_str
, expr
966 # Patterns for _try_parse_num_and_attr()
967 _val_expr_pat
= re
.compile(r
"([^}:]+):\s*")
968 _fl_num_len_attr_pat
= re
.compile(r
"8|16|24|32|40|48|56|64")
969 _leb128_int_attr_pat
= re
.compile(r
"(u|s)leb128")
971 # Tries to parse a value and attribute (fixed length in bits or
972 # `leb128`), returning a value item on success.
973 def _try_parse_num_and_attr(self
):
974 begin_text_loc
= self
._text
_loc
977 m_expr
= self
._try
_parse
_pat
(self
._val
_expr
_pat
)
983 # Create an expression node from the expression string
984 expr_str
, expr
= self
._ast
_expr
_from
_str
(m_expr
.group(1), begin_text_loc
)
987 m_attr
= self
._try
_parse
_pat
(self
._fl
_num
_len
_attr
_pat
)
991 m_attr
= self
._try
_parse
_pat
(self
._leb
128_int
_attr
_pat
)
994 # At this point it's invalid
996 "Expecting a length (multiple of eight bits), `uleb128`, or `sleb128`"
999 # Return LEB128 integer item
1000 cls
= _ULeb128Int
if m_attr
.group(1) == "u" else _SLeb128Int
1001 return cls(expr_str
, expr
, begin_text_loc
)
1003 # Return fixed-length number item
1007 int(m_attr
.group(0)),
1011 # Patterns for _try_parse_var_assign()
1012 _var_assign_name_equal_pat
= re
.compile(r
"({})\s*=".format(_py_name_pat
.pattern
))
1013 _var_assign_expr_pat
= re
.compile(r
"[^}]+")
1015 # Tries to parse a variable assignment, returning a variable
1016 # assignment item on success.
1017 def _try_parse_var_assign(self
):
1018 begin_text_loc
= self
._text
_loc
1021 m
= self
._try
_parse
_pat
(self
._var
_assign
_name
_equal
_pat
)
1030 if name
== _icitte_name
:
1032 "`{}` is a reserved variable name".format(_icitte_name
), begin_text_loc
1035 if name
in self
._label
_names
:
1036 _raise_error("Existing label named `{}`".format(name
), begin_text_loc
)
1038 # Expect an expression
1040 m
= self
._expect
_pat
(self
._var
_assign
_expr
_pat
, "Expecting an expression")
1042 # Create an expression node from the expression string
1043 expr_str
, expr
= self
._ast
_expr
_from
_str
(m
.group(0), begin_text_loc
)
1045 # Add to known variable names
1046 self
._var
_names
.add(name
)
1056 # Pattern for _try_parse_set_bo()
1057 _bo_pat
= re
.compile(r
"[bl]e")
1059 # Tries to parse a byte order name, returning a byte order setting
1061 def _try_parse_set_bo(self
):
1062 begin_text_loc
= self
._text
_loc
1065 m
= self
._try
_parse
_pat
(self
._bo
_pat
)
1071 # Return corresponding item
1072 if m
.group(0) == "be":
1073 return _SetBo(ByteOrder
.BE
, begin_text_loc
)
1075 assert m
.group(0) == "le"
1076 return _SetBo(ByteOrder
.LE
, begin_text_loc
)
1078 # Patterns for _try_parse_val_or_bo()
1079 _val_var_assign_set_bo_prefix_pat
= re
.compile(r
"\{")
1080 _val_var_assign_set_bo_suffix_pat
= re
.compile(r
"\}")
1082 # Tries to parse a value, a variable assignment, or a byte order
1083 # setting, returning an item on success.
1084 def _try_parse_val_or_var_assign_or_set_bo(self
):
1086 if self
._try
_parse
_pat
(self
._val
_var
_assign
_set
_bo
_prefix
_pat
) is None:
1092 # Variable assignment item?
1093 item
= self
._try
_parse
_var
_assign
()
1097 item
= self
._try
_parse
_num
_and
_attr
()
1100 # Byte order setting item?
1101 item
= self
._try
_parse
_set
_bo
()
1104 # At this point it's invalid
1106 "Expecting a fixed-length number, a variable assignment, or a byte order setting"
1111 self
._expect
_pat
(self
._val
_var
_assign
_set
_bo
_suffix
_pat
, "Expecting `}`")
1114 # Tries to parse an offset setting value (after the initial `<`),
1115 # returning an offset item on success.
1116 def _try_parse_set_offset_val(self
):
1117 begin_text_loc
= self
._text
_loc
1120 m
= self
._try
_parse
_pat
(_pos_const_int_pat
)
1127 return _SetOffset(int(_norm_const_int(m
.group(0)), 0), begin_text_loc
)
1129 # Tries to parse a label name (after the initial `<`), returning a
1130 # label item on success.
1131 def _try_parse_label_name(self
):
1132 begin_text_loc
= self
._text
_loc
1135 m
= self
._try
_parse
_pat
(_py_name_pat
)
1144 if name
== _icitte_name
:
1146 "`{}` is a reserved label name".format(_icitte_name
), begin_text_loc
1149 if name
in self
._label
_names
:
1150 _raise_error("Duplicate label name `{}`".format(name
), begin_text_loc
)
1152 if name
in self
._var
_names
:
1153 _raise_error("Existing variable named `{}`".format(name
), begin_text_loc
)
1155 # Add to known label names
1156 self
._label
_names
.add(name
)
1159 return _Label(name
, begin_text_loc
)
1161 # Patterns for _try_parse_label_or_set_offset()
1162 _label_set_offset_prefix_pat
= re
.compile(r
"<")
1163 _label_set_offset_suffix_pat
= re
.compile(r
">")
1165 # Tries to parse a label or an offset setting, returning an item on
1167 def _try_parse_label_or_set_offset(self
):
1169 if self
._try
_parse
_pat
(self
._label
_set
_offset
_prefix
_pat
) is None:
1173 # Offset setting item?
1175 item
= self
._try
_parse
_set
_offset
_val
()
1179 item
= self
._try
_parse
_label
_name
()
1182 # At this point it's invalid
1183 self
._raise
_error
("Expecting a label name or an offset setting value")
1187 self
._expect
_pat
(self
._label
_set
_offset
_suffix
_pat
, "Expecting `>`")
1190 # Pattern for _parse_pad_val()
1191 _pad_val_prefix_pat
= re
.compile(r
"~")
1193 # Tries to parse a padding value, returning the padding value, or 0
1195 def _parse_pad_val(self
):
1200 if self
._try
_parse
_pat
(self
._pad
_val
_prefix
_pat
) is not None:
1202 pad_val_text_loc
= self
._text
_loc
1203 m
= self
._expect
_pat
(
1205 "Expecting a positive constant integer (byte value)",
1209 pad_val
= int(_norm_const_int(m
.group(0)), 0)
1213 "Invalid padding byte value {}".format(pad_val
),
1219 # Patterns for _try_parse_align_offset()
1220 _align_offset_prefix_pat
= re
.compile(r
"@")
1221 _align_offset_val_pat
= re
.compile(r
"\d+")
1223 # Tries to parse an offset alignment, returning an offset alignment
1225 def _try_parse_align_offset(self
):
1226 begin_text_loc
= self
._text
_loc
1229 if self
._try
_parse
_pat
(self
._align
_offset
_prefix
_pat
) is None:
1233 # Expect an alignment
1235 align_text_loc
= self
._text
_loc
1236 m
= self
._expect
_pat
(
1237 self
._align
_offset
_val
_pat
,
1238 "Expecting an alignment (positive multiple of eight bits)",
1241 # Validate alignment
1242 val
= int(m
.group(0))
1244 if val
<= 0 or (val
% 8) != 0:
1246 "Invalid alignment value {} (not a positive multiple of eight)".format(
1253 pad_val
= self
._parse
_pad
_val
()
1256 return _AlignOffset(val
, pad_val
, begin_text_loc
)
1258 # Patterns for _expect_expr()
1259 _inner_expr_prefix_pat
= re
.compile(r
"\{")
1260 _inner_expr_pat
= re
.compile(r
"[^}]+")
1261 _inner_expr_suffix_pat
= re
.compile(r
"\}")
1263 # Parses an expression outside a `{`/`}` context.
1265 # This function accepts:
1267 # • A Python expression within `{` and `}`.
1269 # • A Python name.
1271 # • If `accept_const_int` is `True`: a constant integer, which may
1272 # be negative if `allow_neg_int` is `True`.
1274 # • If `accept_float` is `True`: a constant floating point number.
1276 # Returns the stripped expression string and AST expression.
1279 accept_const_int
: bool = False,
1280 allow_neg_int
: bool = False,
1281 accept_const_float
: bool = False,
1283 begin_text_loc
= self
._text
_loc
1285 # Constant floating point number?
1286 if accept_const_float
:
1287 m
= self
._try
_parse
_pat
(_const_float_pat
)
1290 return self
._ast
_expr
_from
_str
(m
.group(0), begin_text_loc
)
1293 if accept_const_int
:
1294 m
= self
._try
_parse
_pat
(_const_int_pat
)
1297 # Negative and allowed?
1298 if m
.group("neg") == "-" and not allow_neg_int
:
1300 "Expecting a positive constant integer", begin_text_loc
1303 expr_str
= _norm_const_int(m
.group(0))
1304 return self
._ast
_expr
_from
_str
(expr_str
, begin_text_loc
)
1307 m
= self
._try
_parse
_pat
(_py_name_pat
)
1310 return self
._ast
_expr
_from
_str
(m
.group(0), begin_text_loc
)
1313 msg_accepted_parts
= ["a name", "or `{`"]
1315 if accept_const_float
:
1316 msg_accepted_parts
.insert(0, "a constant floating point number")
1318 if accept_const_int
:
1319 msg_pos
= "" if allow_neg_int
else "positive "
1320 msg_accepted_parts
.insert(0, "a {}constant integer".format(msg_pos
))
1322 if len(msg_accepted_parts
) == 2:
1323 msg_accepted
= " ".join(msg_accepted_parts
)
1325 msg_accepted
= ", ".join(msg_accepted_parts
)
1328 self
._inner
_expr
_prefix
_pat
,
1329 "Expecting {}".format(msg_accepted
),
1332 # Expect an expression
1334 expr_text_loc
= self
._text
_loc
1335 m
= self
._expect
_pat
(self
._inner
_expr
_pat
, "Expecting an expression")
1336 expr_str
= m
.group(0)
1340 self
._expect
_pat
(self
._inner
_expr
_suffix
_pat
, "Expecting `}`")
1342 return self
._ast
_expr
_from
_str
(expr_str
, expr_text_loc
)
1344 # Patterns for _try_parse_fill_until()
1345 _fill_until_prefix_pat
= re
.compile(r
"\+")
1346 _fill_until_pad_val_prefix_pat
= re
.compile(r
"~")
1348 # Tries to parse a filling, returning a filling item on success.
1349 def _try_parse_fill_until(self
):
1350 begin_text_loc
= self
._text
_loc
1353 if self
._try
_parse
_pat
(self
._fill
_until
_prefix
_pat
) is None:
1359 expr_str
, expr
= self
._expect
_expr
(accept_const_int
=True)
1362 pad_val
= self
._parse
_pad
_val
()
1365 return _FillUntil(expr_str
, expr
, pad_val
, begin_text_loc
)
1367 # Parses the multiplier expression of a repetition (block or
1368 # post-item) and returns the expression string and AST node.
1369 def _expect_rep_mul_expr(self
):
1370 return self
._expect
_expr
(accept_const_int
=True)
1372 # Common block end pattern
1373 _block_end_pat
= re
.compile(r
"!end\b")
1375 # Pattern for _try_parse_rep_block()
1376 _rep_block_prefix_pat
= re
.compile(r
"!r(?:epeat)?\b")
1378 # Tries to parse a repetition block, returning a repetition item on
1380 def _try_parse_rep_block(self
):
1381 begin_text_loc
= self
._text
_loc
1384 if self
._try
_parse
_pat
(self
._rep
_block
_prefix
_pat
) is None:
1389 self
._skip
_ws
_and
_comments
()
1390 expr_str
, expr
= self
._expect
_rep
_mul
_expr
()
1393 self
._skip
_ws
_and
_comments
()
1394 items_text_loc
= self
._text
_loc
1395 items
= self
._parse
_items
()
1397 # Expect end of block
1398 self
._skip
_ws
_and
_comments
()
1400 self
._block
_end
_pat
, "Expecting an item or `!end` (end of repetition block)"
1404 return _Rep(_Group(items
, items_text_loc
), expr_str
, expr
, begin_text_loc
)
1406 # Pattern for _try_parse_cond_block()
1407 _cond_block_prefix_pat
= re
.compile(r
"!if\b")
1408 _cond_block_else_pat
= re
.compile(r
"!else\b")
1410 # Tries to parse a conditional block, returning a conditional item
1412 def _try_parse_cond_block(self
):
1413 begin_text_loc
= self
._text
_loc
1416 if self
._try
_parse
_pat
(self
._cond
_block
_prefix
_pat
) is None:
1421 self
._skip
_ws
_and
_comments
()
1422 expr_str
, expr
= self
._expect
_expr
()
1424 # Parse "true" items
1425 self
._skip
_ws
_and
_comments
()
1426 true_items_text_loc
= self
._text
_loc
1427 true_items
= self
._parse
_items
()
1428 false_items
= [] # type: List[_Item]
1429 false_items_text_loc
= begin_text_loc
1432 self
._skip
_ws
_and
_comments
()
1434 if self
._try
_parse
_pat
(self
._cond
_block
_else
_pat
) is not None:
1435 # Parse "false" items
1436 self
._skip
_ws
_and
_comments
()
1437 false_items_text_loc
= self
._text
_loc
1438 false_items
= self
._parse
_items
()
1440 # Expect end of block
1442 self
._block
_end
_pat
,
1443 "Expecting an item, `!else`, or `!end` (end of conditional block)",
1448 _Group(true_items
, true_items_text_loc
),
1449 _Group(false_items
, false_items_text_loc
),
1455 # Common left parenthesis pattern
1456 _left_paren_pat
= re
.compile(r
"\(")
1458 # Patterns for _try_parse_macro_def() and _try_parse_macro_exp()
1459 _macro_params_comma_pat
= re
.compile(",")
1461 # Patterns for _try_parse_macro_def()
1462 _macro_def_prefix_pat
= re
.compile(r
"!m(?:acro)?\b")
1464 # Tries to parse a macro definition, adding it to `self._macro_defs`
1465 # and returning `True` on success.
1466 def _try_parse_macro_def(self
):
1467 begin_text_loc
= self
._text
_loc
1470 if self
._try
_parse
_pat
(self
._macro
_def
_prefix
_pat
) is None:
1476 name_text_loc
= self
._text
_loc
1477 m
= self
._expect
_pat
(_py_name_pat
, "Expecting a valid macro name")
1482 if name
in self
._macro
_defs
:
1483 _raise_error("Duplicate macro named `{}`".format(name
), name_text_loc
)
1487 self
._expect
_pat
(self
._left
_paren
_pat
, "Expecting `(`")
1489 # Try to parse comma-separated parameter names
1490 param_names
= [] # type: List[str]
1491 expect_comma
= False
1497 if self
._try
_parse
_pat
(self
._right
_paren
_pat
) is not None:
1503 self
._expect
_pat
(self
._macro
_params
_comma
_pat
, "Expecting `,`")
1505 # Expect parameter name
1507 param_text_loc
= self
._text
_loc
1508 m
= self
._expect
_pat
(_py_name_pat
, "Expecting valid parameter name")
1510 if m
.group(0) in param_names
:
1512 "Duplicate macro parameter named `{}`".format(m
.group(0)),
1516 param_names
.append(m
.group(0))
1520 self
._skip
_ws
_and
_comments
()
1521 items_text_loc
= self
._text
_loc
1522 old_var_names
= self
._var
_names
.copy()
1523 old_label_names
= self
._label
_names
.copy()
1524 self
._var
_names
= set() # type: Set[str]
1525 self
._label
_names
= set() # type: Set[str]
1526 items
= self
._parse
_items
()
1527 self
._var
_names
= old_var_names
1528 self
._label
_names
= old_label_names
1532 self
._block
_end
_pat
, "Expecting an item or `!end` (end of macro block)"
1536 self
._macro
_defs
[name
] = _MacroDef(
1537 name
, param_names
, _Group(items
, items_text_loc
), begin_text_loc
1542 # Patterns for _try_parse_macro_exp()
1543 _macro_exp_prefix_pat
= re
.compile(r
"m\b")
1544 _macro_exp_colon_pat
= re
.compile(r
":")
1546 # Tries to parse a macro expansion, returning a macro expansion item
1548 def _try_parse_macro_exp(self
):
1549 begin_text_loc
= self
._text
_loc
1552 if self
._try
_parse
_pat
(self
._macro
_exp
_prefix
_pat
) is None:
1558 self
._expect
_pat
(self
._macro
_exp
_colon
_pat
, "Expecting `:`")
1560 # Expect a macro name
1562 name_text_loc
= self
._text
_loc
1563 m
= self
._expect
_pat
(_py_name_pat
, "Expecting a valid macro name")
1567 macro_def
= self
._macro
_defs
.get(name
)
1569 if macro_def
is None:
1570 _raise_error("Unknown macro name `{}`".format(name
), name_text_loc
)
1574 self
._expect
_pat
(self
._left
_paren
_pat
, "Expecting `(`")
1576 # Try to parse comma-separated parameter values
1577 params_text_loc
= self
._text
_loc
1578 params
= [] # type: List[_MacroExpParam]
1579 expect_comma
= False
1585 if self
._try
_parse
_pat
(self
._right
_paren
_pat
) is not None:
1591 self
._expect
_pat
(self
._macro
_params
_comma
_pat
, "Expecting `,`")
1594 param_text_loc
= self
._text
_loc
1598 accept_const_int
=True,
1600 accept_const_float
=True,
1602 text_loc
=param_text_loc
1607 # Validate parameter values
1608 if len(params
) != len(macro_def
.param_names
):
1609 sing_plur
= "" if len(params
) == 1 else "s"
1611 "Macro expansion passes {} parameter{} while the definition expects {}".format(
1612 len(params
), sing_plur
, len(macro_def
.param_names
)
1618 return _MacroExp(name
, params
, begin_text_loc
)
1620 # Tries to parse a base item (anything except a repetition),
1621 # returning it on success.
1622 def _try_parse_base_item(self
):
1624 item
= self
._try
_parse
_byte
()
1626 if item
is not None:
1630 item
= self
._try
_parse
_str
()
1632 if item
is not None:
1635 # Value, variable assignment, or byte order setting item?
1636 item
= self
._try
_parse
_val
_or
_var
_assign
_or
_set
_bo
()
1638 if item
is not None:
1641 # Label or offset setting item?
1642 item
= self
._try
_parse
_label
_or
_set
_offset
()
1644 if item
is not None:
1647 # Offset alignment item?
1648 item
= self
._try
_parse
_align
_offset
()
1650 if item
is not None:
1654 item
= self
._try
_parse
_fill
_until
()
1656 if item
is not None:
1660 item
= self
._try
_parse
_group
()
1662 if item
is not None:
1665 # Repetition block item?
1666 item
= self
._try
_parse
_rep
_block
()
1668 if item
is not None:
1671 # Conditional block item?
1672 item
= self
._try
_parse
_cond
_block
()
1674 if item
is not None:
1678 item
= self
._try
_parse
_macro
_exp
()
1680 if item
is not None:
1683 # Pattern for _try_parse_rep_post()
1684 _rep_post_prefix_pat
= re
.compile(r
"\*")
1686 # Tries to parse a post-item repetition, returning the expression
1687 # string and AST expression node on success.
1688 def _try_parse_rep_post(self
):
1690 if self
._try
_parse
_pat
(self
._rep
_post
_prefix
_pat
) is None:
1694 # Return expression string and AST expression
1695 self
._skip
_ws
_and
_comments
()
1696 return self
._expect
_rep
_mul
_expr
()
1698 # Tries to parse an item, possibly followed by a repetition,
1699 # returning `True` on success.
1701 # Appends any parsed item to `items`.
1702 def _try_append_item(self
, items
: List
[_Item
]):
1703 self
._skip
_ws
_and
_comments
()
1706 item
= self
._try
_parse
_base
_item
()
1711 # Parse repetition if the base item is repeatable
1712 if isinstance(item
, _RepableItem
):
1713 self
._skip
_ws
_and
_comments
()
1714 rep_text_loc
= self
._text
_loc
1715 rep_ret
= self
._try
_parse
_rep
_post
()
1717 if rep_ret
is not None:
1718 item
= _Rep(item
, *rep_ret
, text_loc
=rep_text_loc
)
1723 # Parses and returns items, skipping whitespaces, insignificant
1724 # symbols, and comments when allowed, and stopping at the first
1725 # unknown character.
1727 # Accepts and registers macro definitions if `accept_macro_defs`
1729 def _parse_items(self
, accept_macro_defs
: bool = False) -> List
[_Item
]:
1730 items
= [] # type: List[_Item]
1732 while self
._isnt
_done
():
1733 # Try to append item
1734 if not self
._try
_append
_item
(items
):
1735 if accept_macro_defs
and self
._try
_parse
_macro
_def
():
1738 # Unknown at this point
1743 # Parses the whole Normand input, setting `self._res` to the main
1744 # group item on success.
1746 if len(self
._normand
.strip()) == 0:
1747 # Special case to make sure there's something to consume
1748 self
._res
= _Group([], self
._text
_loc
)
1751 # Parse first level items
1752 items
= self
._parse
_items
(True)
1754 # Make sure there's nothing left
1755 self
._skip
_ws
_and
_comments
()
1757 if self
._isnt
_done
():
1759 "Unexpected character `{}`".format(self
._normand
[self
._at
])
1762 # Set main group item
1763 self
._res
= _Group(items
, self
._text
_loc
)
1766 # The return type of parse().
1772 variables
: VariablesT
,
1775 bo
: Optional
[ByteOrder
],
1777 self
= cls
.__new
__(cls
)
1778 self
._init
(data
, variables
, labels
, offset
, bo
)
1781 def __init__(self
, *args
, **kwargs
): # type: ignore
1782 raise NotImplementedError
1787 variables
: VariablesT
,
1790 bo
: Optional
[ByteOrder
],
1793 self
._vars
= variables
1794 self
._labels
= labels
1795 self
._offset
= offset
1803 # Dictionary of updated variable names to their last computed value.
1805 def variables(self
):
1808 # Dictionary of updated main group label names to their computed
1819 # Updated byte order.
1821 def byte_order(self
):
1825 # Raises a parse error for the item `item`, creating it using the
1827 def _raise_error_for_item(msg
: str, item
: _Item
) -> NoReturn
:
1828 _raise_error(msg
, item
.text_loc
)
1831 # The `ICITTE` reserved name.
1832 _icitte_name
= "ICITTE"
1835 # Base node visitor.
1837 # Calls the _visit_name() method for each name node which isn't the name
1839 class _NodeVisitor(ast
.NodeVisitor
):
1841 self
._parent
_is
_call
= False
1843 def generic_visit(self
, node
: ast
.AST
):
1844 if type(node
) is ast
.Call
:
1845 self
._parent
_is
_call
= True
1846 elif type(node
) is ast
.Name
and not self
._parent
_is
_call
:
1847 self
._visit
_name
(node
.id)
1849 super().generic_visit(node
)
1850 self
._parent
_is
_call
= False
1853 def _visit_name(self
, name
: str):
1857 # Expression validator: validates that all the names within the
1858 # expression are allowed.
1859 class _ExprValidator(_NodeVisitor
):
1860 def __init__(self
, expr_str
: str, text_loc
: TextLocation
, allowed_names
: Set
[str]):
1862 self
._expr
_str
= expr_str
1863 self
._text
_loc
= text_loc
1864 self
._allowed
_names
= allowed_names
1866 def _visit_name(self
, name
: str):
1867 # Make sure the name refers to a known and reachable
1868 # variable/label name.
1869 if name
!= _icitte_name
and name
not in self
._allowed
_names
:
1870 msg
= "Illegal (unknown or unreachable) variable/label name `{}` in expression `{}`".format(
1871 name
, self
._expr
_str
1874 allowed_names
= self
._allowed
_names
.copy()
1875 allowed_names
.add(_icitte_name
)
1877 if len(allowed_names
) > 0:
1878 allowed_names_str
= ", ".join(
1879 sorted(["`{}`".format(name
) for name
in allowed_names
])
1881 msg
+= "; the legal names are {{{}}}".format(allowed_names_str
)
1893 variables
: VariablesT
,
1896 bo
: Optional
[ByteOrder
],
1898 self
.variables
= variables
.copy()
1899 self
.labels
= labels
.copy()
1900 self
.offset
= offset
1904 return "_GenState({}, {}, {}, {})".format(
1905 repr(self
.variables
), repr(self
.labels
), repr(self
.offset
), repr(self
.bo
)
1909 # Fixed-length number item instance.
1910 class _FlNumItemInst
:
1914 offset_in_data
: int,
1916 parse_error_msgs
: List
[ParseErrorMessage
],
1919 self
._offset
_in
_data
= offset_in_data
1921 self
._parse
_error
_msgs
= parse_error_msgs
1928 def offset_in_data(self
):
1929 return self
._offset
_in
_data
1936 def parse_error_msgs(self
):
1937 return self
._parse
_error
_msgs
1940 # Generator of data and final state from a group item.
1942 # Generation happens in memory at construction time. After building, use
1943 # the `data`, `variables`, `labels`, `offset`, and `bo` properties to
1944 # get the resulting context.
1946 # The steps of generation are:
1948 # 1. Handle each item in prefix order.
1950 # The handlers append bytes to `self._data` and update some current
1951 # state object (`_GenState` instance).
1953 # When handling a fixed-length number item, try to evaluate its
1954 # expression using the current state. If this fails, then it might be
1955 # because the expression refers to a "future" label: save the current
1956 # offset in `self._data` (generated data) and a snapshot of the
1957 # current state within `self._fl_num_item_insts` (`_FlNumItemInst`
1958 # object). _gen_fl_num_item_insts() will deal with this later. A
1959 # `_FlNumItemInst` instance also contains a snapshot of the current
1960 # parsing error messages (`self._parse_error_msgs`) which need to be
1961 # taken into account when handling the instance later.
1963 # When handling the items of a group, keep a map of immediate label
1964 # names to their offset. Then, after having processed all the items,
1965 # update the relevant saved state snapshots in
1966 # `self._fl_num_item_insts` with those immediate label values.
1967 # _gen_fl_num_item_insts() will deal with this later.
1969 # 2. Handle all the fixed-length number item instances of which the
1970 # expression evaluation failed before.
1972 # At this point, `self._fl_num_item_insts` contains everything that's
1973 # needed to evaluate the expressions, including the values of
1974 # "future" labels from the point of view of some fixed-length number
1977 # If an evaluation fails at this point, then it's a user error. Add
1978 # to the parsing error all the saved parsing error messages of the
1979 # instance. Those additional messages add precious context to the
1985 macro_defs
: _MacroDefsT
,
1986 variables
: VariablesT
,
1989 bo
: Optional
[ByteOrder
],
1991 self
._macro
_defs
= macro_defs
1992 self
._fl
_num
_item
_insts
= [] # type: List[_FlNumItemInst]
1993 self
._parse
_error
_msgs
= [] # type: List[ParseErrorMessage]
1994 self
._gen
(group
, _GenState(variables
, labels
, offset
, bo
))
2001 # Updated variables.
2003 def variables(self
):
2004 return self
._final
_state
.variables
2006 # Updated main group labels.
2009 return self
._final
_state
.labels
2014 return self
._final
_state
.offset
2016 # Updated byte order.
2019 return self
._final
_state
.bo
2021 # Evaluates the expression `expr` of which the original string is
2022 # `expr_str` at the location `text_loc` considering the current
2023 # generation state `state`.
2025 # If `allow_float` is `True`, then the type of the result may be
2030 expr
: ast
.Expression
,
2031 text_loc
: TextLocation
,
2033 allow_float
: bool = False,
2035 syms
= {} # type: VariablesT
2036 syms
.update(state
.labels
)
2038 # Set the `ICITTE` name to the current offset
2039 syms
[_icitte_name
] = state
.offset
2041 # Add the current variables
2042 syms
.update(state
.variables
)
2044 # Validate the node and its children
2045 _ExprValidator(expr_str
, text_loc
, set(syms
.keys())).visit(expr
)
2047 # Compile and evaluate expression node
2049 val
= eval(compile(expr
, "", "eval"), None, syms
)
2050 except Exception as exc
:
2052 "Failed to evaluate expression `{}`: {}".format(expr_str
, exc
),
2056 # Convert `bool` result type to `int` to normalize
2057 if type(val
) is bool:
2060 # Validate result type
2061 expected_types
= {int}
# type: Set[type]
2065 expected_types
.add(float)
2066 type_msg
+= " or `float`"
2068 if type(val
) not in expected_types
:
2070 "Invalid expression `{}`: expecting result type {}, not `{}`".format(
2071 expr_str
, type_msg
, type(val
).__name
__
2078 # Evaluates the expression of `item` considering the current
2079 # generation state `state`.
2081 # If `allow_float` is `True`, then the type of the result may be
2084 def _eval_item_expr(
2085 item
: Union
[_FlNum
, _Leb128Int
, _FillUntil
, _VarAssign
, _Rep
, _Cond
],
2087 allow_float
: bool = False,
2089 return _Gen
._eval
_expr
(
2090 item
.expr_str
, item
.expr
, item
.text_loc
, state
, allow_float
2093 # Handles the byte item `item`.
2094 def _handle_byte_item(self
, item
: _Byte
, state
: _GenState
):
2095 self
._data
.append(item
.val
)
2096 state
.offset
+= item
.size
2098 # Handles the string item `item`.
2099 def _handle_str_item(self
, item
: _Str
, state
: _GenState
):
2100 self
._data
+= item
.data
2101 state
.offset
+= item
.size
2103 # Handles the byte order setting item `item`.
2104 def _handle_set_bo_item(self
, item
: _SetBo
, state
: _GenState
):
2105 # Update current byte order
2108 # Handles the variable assignment item `item`.
2109 def _handle_var_assign_item(self
, item
: _VarAssign
, state
: _GenState
):
2111 state
.variables
[item
.name
] = self
._eval
_item
_expr
(item
, state
, True)
2113 # Handles the fixed-length number item `item`.
2114 def _handle_fl_num_item(self
, item
: _FlNum
, state
: _GenState
):
2115 # Validate current byte order
2116 if state
.bo
is None and item
.len > 8:
2117 _raise_error_for_item(
2118 "Current byte order isn't defined at first fixed-length number (`{}`) to encode on more than 8 bits".format(
2124 # Try an immediate evaluation. If it fails, then keep everything
2125 # needed to (try to) generate the bytes of this item later.
2127 data
= self
._gen
_fl
_num
_item
_inst
_data
(item
, state
)
2129 self
._fl
_num
_item
_insts
.append(
2133 copy
.deepcopy(state
),
2134 copy
.deepcopy(self
._parse
_error
_msgs
),
2138 # Reserve space in `self._data` for this instance
2139 data
= bytes([0] * (item
.len // 8))
2145 state
.offset
+= len(data
)
2147 # Returns the size, in bytes, required to encode the value `val`
2148 # with LEB128 (signed version if `is_signed` is `True`).
2150 def _leb128_size_for_val(val
: int, is_signed
: bool):
2152 # Equivalent upper bound.
2154 # For example, if `val` is -128, then the full integer for
2155 # this number of bits would be [-128, 127].
2158 # Number of bits (add one for the sign if needed)
2159 bits
= val
.bit_length() + int(is_signed
)
2164 # Seven bits per byte
2165 return math
.ceil(bits
/ 7)
2167 # Handles the LEB128 integer item `item`.
2168 def _handle_leb128_int_item(self
, item
: _Leb128Int
, state
: _GenState
):
2170 val
= self
._eval
_item
_expr
(item
, state
, False)
2173 size
= self
._leb
128_size
_for
_val
(val
, type(item
) is _SLeb128Int
)
2176 for _
in range(size
):
2177 # Seven LSBs, MSB of the byte set (continue)
2178 self
._data
.append((val
& 0x7F) |
0x80)
2181 # Clear MSB of last byte (stop)
2182 self
._data
[-1] &= ~
0x80
2185 state
.offset
+= size
2187 # Handles the group item `item`, removing the immediate labels from
2188 # `state` at the end if `remove_immediate_labels` is `True`.
2189 def _handle_group_item(
2190 self
, item
: _Group
, state
: _GenState
, remove_immediate_labels
: bool = True
2192 first_fl_num_item_inst_index
= len(self
._fl
_num
_item
_insts
)
2193 immediate_labels
= {} # type: LabelsT
2196 for subitem
in item
.items
:
2197 if type(subitem
) is _Label
:
2198 # Add to local immediate labels
2199 immediate_labels
[subitem
.name
] = state
.offset
2201 self
._handle
_item
(subitem
, state
)
2203 # Remove immediate labels from current state if needed
2204 if remove_immediate_labels
:
2205 for name
in immediate_labels
:
2206 del state
.labels
[name
]
2208 # Add all immediate labels to all state snapshots since
2209 # `first_fl_num_item_inst_index`.
2210 for inst
in self
._fl
_num
_item
_insts
[first_fl_num_item_inst_index
:]:
2211 inst
.state
.labels
.update(immediate_labels
)
2213 # Handles the repetition item `item`.
2214 def _handle_rep_item(self
, item
: _Rep
, state
: _GenState
):
2215 # Compute the repetition count
2216 mul
= _Gen
._eval
_item
_expr
(item
, state
)
2220 _raise_error_for_item(
2221 "Invalid expression `{}`: unexpected negative result {:,}".format(
2227 # Generate item data `mul` times
2228 for _
in range(mul
):
2229 self
._handle
_item
(item
.item
, state
)
2231 # Handles the conditional item `item`.
2232 def _handle_cond_item(self
, item
: _Cond
, state
: _GenState
):
2233 # Compute the conditional value
2234 val
= _Gen
._eval
_item
_expr
(item
, state
)
2236 # Generate item data if needed
2238 self
._handle
_item
(item
.true_item
, state
)
2240 self
._handle
_item
(item
.false_item
, state
)
2242 # Evaluates the parameters of the macro expansion item `item`
2243 # considering the initial state `init_state` and returns a new state
2244 # to handle the items of the macro.
2245 def _eval_macro_exp_params(self
, item
: _MacroExp
, init_state
: _GenState
):
2247 exp_state
= _GenState({}, {}, init_state
.offset
, init_state
.bo
)
2249 # Evaluate the parameter expressions
2250 macro_def
= self
._macro
_defs
[item
.name
]
2252 for param_name
, param
in zip(macro_def
.param_names
, item
.params
):
2253 exp_state
.variables
[param_name
] = _Gen
._eval
_expr
(
2254 param
.expr_str
, param
.expr
, param
.text_loc
, init_state
, True
2259 # Handles the macro expansion item `item`.
2260 def _handle_macro_exp_item(self
, item
: _MacroExp
, state
: _GenState
):
2261 parse_error_msg_text
= "While expanding the macro `{}`:".format(item
.name
)
2265 exp_state
= self
._eval
_macro
_exp
_params
(item
, state
)
2267 # Process the contained group
2268 init_data_size
= len(self
._data
)
2270 ParseErrorMessage
._create
( # pyright: ignore[reportPrivateUsage]
2271 parse_error_msg_text
, item
.text_loc
2274 self
._parse
_error
_msgs
.append(parse_error_msg
)
2275 self
._handle
_item
(self
._macro
_defs
[item
.name
].group
, exp_state
)
2276 self
._parse
_error
_msgs
.pop()
2277 except ParseError
as exc
:
2278 _augment_error(exc
, parse_error_msg_text
, item
.text_loc
)
2280 # Update state offset and return
2281 state
.offset
+= len(self
._data
) - init_data_size
2283 # Handles the offset setting item `item`.
2284 def _handle_set_offset_item(self
, item
: _SetOffset
, state
: _GenState
):
2285 state
.offset
= item
.val
2287 # Handles the offset alignment item `item` (adds padding).
2288 def _handle_align_offset_item(self
, item
: _AlignOffset
, state
: _GenState
):
2289 init_offset
= state
.offset
2290 align_bytes
= item
.val
// 8
2291 state
.offset
= (state
.offset
+ align_bytes
- 1) // align_bytes
* align_bytes
2292 self
._data
+= bytes([item
.pad_val
] * (state
.offset
- init_offset
))
2294 # Handles the filling item `item` (adds padding).
2295 def _handle_fill_until_item(self
, item
: _FillUntil
, state
: _GenState
):
2296 # Compute the new offset
2297 new_offset
= _Gen
._eval
_item
_expr
(item
, state
)
2299 # Validate the new offset
2300 if new_offset
< state
.offset
:
2301 _raise_error_for_item(
2302 "Invalid expression `{}`: new offset {:,} is less than current offset {:,}".format(
2303 item
.expr_str
, new_offset
, state
.offset
2309 self
._data
+= bytes([item
.pad_val
] * (new_offset
- state
.offset
))
2312 state
.offset
= new_offset
2314 # Handles the label item `item`.
2315 def _handle_label_item(self
, item
: _Label
, state
: _GenState
):
2316 state
.labels
[item
.name
] = state
.offset
2318 # Handles the item `item`, returning the updated next repetition
2320 def _handle_item(self
, item
: _Item
, state
: _GenState
):
2321 return self
._item
_handlers
[type(item
)](item
, state
)
2323 # Generates the data for a fixed-length integer item instance having
2324 # the value `val` and returns it.
2325 def _gen_fl_int_item_inst_data(self
, val
: int, item
: _FlNum
, state
: _GenState
):
2327 if val
< -(2 ** (item
.len - 1)) or val
> 2**item
.len - 1:
2328 _raise_error_for_item(
2329 "Value {:,} is outside the {}-bit range when evaluating expression `{}`".format(
2330 val
, item
.len, item
.expr_str
2335 # Encode result on 64 bits (to extend the sign bit whatever the
2336 # value of `item.len`).
2339 ">" if state
.bo
in (None, ByteOrder
.BE
) else "<",
2340 "Q" if val
>= 0 else "q",
2345 # Keep only the requested length
2346 len_bytes
= item
.len // 8
2348 if state
.bo
in (None, ByteOrder
.BE
):
2349 # Big endian: keep last bytes
2350 data
= data
[-len_bytes
:]
2352 # Little endian: keep first bytes
2353 assert state
.bo
== ByteOrder
.LE
2354 data
= data
[:len_bytes
]
2359 # Generates the data for a fixed-length floating point number item
2360 # instance having the value `val` and returns it.
2361 def _gen_fl_float_item_inst_data(self
, val
: float, item
: _FlNum
, state
: _GenState
):
2363 if item
.len not in (32, 64):
2364 _raise_error_for_item(
2365 "Invalid {}-bit length for a fixed-length floating point number (value {:,})".format(
2371 # Encode and return result
2374 ">" if state
.bo
in (None, ByteOrder
.BE
) else "<",
2375 "f" if item
.len == 32 else "d",
2380 # Generates the data for a fixed-length number item instance and
2382 def _gen_fl_num_item_inst_data(self
, item
: _FlNum
, state
: _GenState
):
2384 val
= self
._eval
_item
_expr
(item
, state
, True)
2386 # Handle depending on type
2387 if type(val
) is int:
2388 return self
._gen
_fl
_int
_item
_inst
_data
(val
, item
, state
)
2390 assert type(val
) is float
2391 return self
._gen
_fl
_float
_item
_inst
_data
(val
, item
, state
)
2393 # Generates the data for all the fixed-length number item instances
2394 # and writes it at the correct offset within `self._data`.
2395 def _gen_fl_num_item_insts(self
):
2396 for inst
in self
._fl
_num
_item
_insts
:
2399 data
= self
._gen
_fl
_num
_item
_inst
_data
(inst
.item
, inst
.state
)
2400 except ParseError
as exc
:
2401 # Add all the saved parse error messages for this
2403 for msg
in reversed(inst
.parse_error_msgs
):
2404 _add_error_msg(exc
, msg
.text
, msg
.text_location
)
2408 # Insert bytes into `self._data`
2409 self
._data
[inst
.offset_in_data
: inst
.offset_in_data
+ len(data
)] = data
2411 # Generates the data (`self._data`) and final state
2412 # (`self._final_state`) from `group` and the initial state `state`.
2413 def _gen(self
, group
: _Group
, state
: _GenState
):
2415 self
._data
= bytearray()
2418 self
._item
_handlers
= {
2419 _AlignOffset
: self
._handle
_align
_offset
_item
,
2420 _Byte
: self
._handle
_byte
_item
,
2421 _Cond
: self
._handle
_cond
_item
,
2422 _FillUntil
: self
._handle
_fill
_until
_item
,
2423 _FlNum
: self
._handle
_fl
_num
_item
,
2424 _Group
: self
._handle
_group
_item
,
2425 _Label
: self
._handle
_label
_item
,
2426 _MacroExp
: self
._handle
_macro
_exp
_item
,
2427 _Rep
: self
._handle
_rep
_item
,
2428 _SetBo
: self
._handle
_set
_bo
_item
,
2429 _SetOffset
: self
._handle
_set
_offset
_item
,
2430 _SLeb128Int
: self
._handle
_leb
128_int
_item
,
2431 _Str
: self
._handle
_str
_item
,
2432 _ULeb128Int
: self
._handle
_leb
128_int
_item
,
2433 _VarAssign
: self
._handle
_var
_assign
_item
,
2434 } # type: Dict[type, Callable[[Any, _GenState], None]]
2436 # Handle the group item, _not_ removing the immediate labels
2437 # because the `labels` property offers them.
2438 self
._handle
_group
_item
(group
, state
, False)
2440 # This is actually the final state
2441 self
._final
_state
= state
2443 # Generate all the fixed-length number bytes now that we know
2445 self
._gen
_fl
_num
_item
_insts
()
2448 # Returns a `ParseResult` instance containing the bytes encoded by the
2449 # input string `normand`.
2451 # `init_variables` is a dictionary of initial variable names (valid
2452 # Python names) to integral values. A variable name must not be the
2453 # reserved name `ICITTE`.
2455 # `init_labels` is a dictionary of initial label names (valid Python
2456 # names) to integral values. A label name must not be the reserved name
2459 # `init_offset` is the initial offset.
2461 # `init_byte_order` is the initial byte order.
2463 # Raises `ParseError` on any parsing error.
2466 init_variables
: Optional
[VariablesT
] = None,
2467 init_labels
: Optional
[LabelsT
] = None,
2468 init_offset
: int = 0,
2469 init_byte_order
: Optional
[ByteOrder
] = None,
2471 if init_variables
is None:
2474 if init_labels
is None:
2477 parser
= _Parser(normand
, init_variables
, init_labels
)
2486 return ParseResult
._create
( # pyright: ignore[reportPrivateUsage]
2487 gen
.data
, gen
.variables
, gen
.labels
, gen
.offset
, gen
.bo
2491 # Raises a command-line error with the message `msg`.
2492 def _raise_cli_error(msg
: str) -> NoReturn
:
2493 raise RuntimeError("Command-line error: {}".format(msg
))
2496 # Returns the `int` or `float` value out of a CLI assignment value.
2497 def _val_from_assign_val_str(s
: str, is_label
: bool):
2500 # Floating point number?
2502 m
= _const_float_pat
.fullmatch(s
)
2505 return float(m
.group(0))
2508 m
= _const_int_pat
.fullmatch(s
)
2511 return int(_norm_const_int(m
.group(0)), 0)
2513 exp
= "an integer" if is_label
else "a number"
2514 _raise_cli_error("Invalid assignment value `{}`: expecting {}".format(s
, exp
))
2517 # Returns a dictionary of string to numbers from the list of strings
2518 # `args` containing `NAME=VAL` entries.
2519 def _dict_from_arg(args
: Optional
[List
[str]], is_label
: bool):
2520 d
= {} # type: VariablesT
2526 m
= re
.match(r
"({})\s*=\s*(.+)$".format(_py_name_pat
.pattern
), arg
)
2529 _raise_cli_error("Invalid assignment `{}`".format(arg
))
2531 d
[m
.group(1)] = _val_from_assign_val_str(m
.group(2), is_label
)
2536 # Parses the command-line arguments and returns, in this order:
2538 # 1. The input file path, or `None` if none.
2539 # 2. The Normand input text.
2540 # 3. The initial offset.
2541 # 4. The initial byte order.
2542 # 5. The initial variables.
2543 # 6. The initial labels.
2544 def _parse_cli_args():
2548 ap
= argparse
.ArgumentParser()
2555 help="initial offset (positive)",
2561 choices
=["be", "le"],
2563 help="initial byte order (`be` or `le`)",
2570 help="add an initial variable (may be repeated)",
2577 help="add an initial label (may be repeated)",
2580 "--version", action
="version", version
="Normand {}".format(__version__
)
2587 help="input path (none means standard input)",
2591 args
= ap
.parse_args()
2594 if args
.path
is None:
2595 normand
= sys
.stdin
.read()
2597 with
open(args
.path
) as f
:
2600 # Variables and labels
2601 variables
= _dict_from_arg(args
.var
, False)
2602 labels
= _dict_from_arg(args
.label
, True)
2606 _raise_cli_error("Invalid negative offset {}")
2608 # Validate and set byte order
2609 bo
= None # type: Optional[ByteOrder]
2611 if args
.byte_order
is not None:
2612 if args
.byte_order
== "be":
2615 assert args
.byte_order
== "le"
2618 # Return input and initial state
2619 return args
.path
, normand
, args
.offset
, bo
, variables
, typing
.cast(LabelsT
, labels
)
2622 # CLI entry point without exception handling.
2623 def _run_cli_with_args(
2626 bo
: Optional
[ByteOrder
],
2627 variables
: VariablesT
,
2630 sys
.stdout
.buffer.write(parse(normand
, variables
, labels
, offset
, bo
).data
)
2633 # Prints the exception message `msg` and exits with status 1.
2634 def _fail(msg
: str) -> NoReturn
:
2635 if not msg
.endswith("."):
2638 print(msg
.strip(), file=sys
.stderr
)
2645 args
= _parse_cli_args()
2646 except Exception as exc
:
2650 _run_cli_with_args(*args
[1:])
2651 except ParseError
as exc
:
2654 prefix
= "" if args
[0] is None else "{}:".format(os
.path
.abspath(args
[0]))
2657 for msg
in reversed(exc
.messages
):
2658 fail_msg
+= "{}{}:{} - {}".format(
2660 msg
.text_location
.line_no
,
2661 msg
.text_location
.col_no
,
2665 if fail_msg
[-1] not in ".:;":
2670 _fail(fail_msg
.strip())
2671 except Exception as exc
:
2675 if __name__
== "__main__":