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.11.0"
55 from typing
import Any
, Set
, Dict
, List
, Union
, Pattern
, Callable
, NoReturn
, Optional
58 # Text location (line and column numbers).
61 def _create(cls
, line_no
: int, col_no
: int):
62 self
= cls
.__new
__(cls
)
63 self
._init
(line_no
, col_no
)
66 def __init__(*args
, **kwargs
): # type: ignore
67 raise NotImplementedError
69 def _init(self
, line_no
: int, col_no
: int):
70 self
._line
_no
= line_no
84 return "TextLocation({}, {})".format(self
._line
_no
, self
._col
_no
)
89 def __init__(self
, text_loc
: TextLocation
):
90 self
._text
_loc
= text_loc
92 # Source text location.
99 class _ScalarItem(_Item
):
100 # Returns the size, in bytes, of this item.
103 def size(self
) -> int:
113 class _Byte(_ScalarItem
, _RepableItem
):
114 def __init__(self
, val
: int, text_loc
: TextLocation
):
115 super().__init
__(text_loc
)
128 return "_Byte({}, {})".format(hex(self
._val
), repr(self
._text
_loc
))
132 class _Str(_ScalarItem
, _RepableItem
):
133 def __init__(self
, data
: bytes
, text_loc
: TextLocation
):
134 super().__init
__(text_loc
)
144 return len(self
._data
)
147 return "_Str({}, {})".format(repr(self
._data
), repr(self
._text
_loc
))
152 class ByteOrder(enum
.Enum
):
160 # Byte order setting.
162 def __init__(self
, bo
: ByteOrder
, text_loc
: TextLocation
):
163 super().__init
__(text_loc
)
171 return "_SetBo({}, {})".format(repr(self
._bo
), repr(self
._text
_loc
))
176 def __init__(self
, name
: str, text_loc
: TextLocation
):
177 super().__init
__(text_loc
)
186 return "_Label({}, {})".format(repr(self
._name
), repr(self
._text
_loc
))
190 class _SetOffset(_Item
):
191 def __init__(self
, val
: int, text_loc
: TextLocation
):
192 super().__init
__(text_loc
)
195 # Offset value (bytes).
201 return "_SetOffset({}, {})".format(repr(self
._val
), repr(self
._text
_loc
))
205 class _AlignOffset(_Item
):
206 def __init__(self
, val
: int, pad_val
: int, text_loc
: TextLocation
):
207 super().__init
__(text_loc
)
209 self
._pad
_val
= pad_val
211 # Alignment value (bits).
216 # Padding byte value.
222 return "_AlignOffset({}, {}, {})".format(
223 repr(self
._val
), repr(self
._pad
_val
), repr(self
._text
_loc
)
227 # Mixin of containing an AST expression and its string.
229 def __init__(self
, expr_str
: str, expr
: ast
.Expression
):
230 self
._expr
_str
= expr_str
236 return self
._expr
_str
238 # Expression node to evaluate.
244 # Variable assignment.
245 class _VarAssign(_Item
, _ExprMixin
):
247 self
, name
: str, expr_str
: str, expr
: ast
.Expression
, text_loc
: TextLocation
249 super().__init
__(text_loc
)
250 _ExprMixin
.__init
__(self
, expr_str
, expr
)
259 return "_VarAssign({}, {}, {}, {})".format(
261 repr(self
._expr
_str
),
263 repr(self
._text
_loc
),
267 # Fixed-length number, possibly needing more than one byte.
268 class _FlNum(_ScalarItem
, _RepableItem
, _ExprMixin
):
270 self
, expr_str
: str, expr
: ast
.Expression
, len: int, text_loc
: TextLocation
272 super().__init
__(text_loc
)
273 _ExprMixin
.__init
__(self
, expr_str
, expr
)
283 return self
._len
// 8
286 return "_FlNum({}, {}, {}, {})".format(
287 repr(self
._expr
_str
),
290 repr(self
._text
_loc
),
295 class _Leb128Int(_Item
, _RepableItem
, _ExprMixin
):
296 def __init__(self
, expr_str
: str, expr
: ast
.Expression
, text_loc
: TextLocation
):
297 super().__init
__(text_loc
)
298 _ExprMixin
.__init
__(self
, expr_str
, expr
)
301 return "{}({}, {}, {})".format(
302 self
.__class
__.__name
__,
303 repr(self
._expr
_str
),
305 repr(self
._text
_loc
),
309 # Unsigned LEB128 integer.
310 class _ULeb128Int(_Leb128Int
, _RepableItem
, _ExprMixin
):
314 # Signed LEB128 integer.
315 class _SLeb128Int(_Leb128Int
, _RepableItem
, _ExprMixin
):
320 class _Group(_Item
, _RepableItem
):
321 def __init__(self
, items
: List
[_Item
], text_loc
: TextLocation
):
322 super().__init
__(text_loc
)
331 return "_Group({}, {})".format(repr(self
._items
), repr(self
._text
_loc
))
335 class _Rep(_Item
, _ExprMixin
):
337 self
, item
: _Item
, expr_str
: str, expr
: ast
.Expression
, text_loc
: TextLocation
339 super().__init
__(text_loc
)
340 _ExprMixin
.__init
__(self
, expr_str
, expr
)
349 return "_Rep({}, {}, {}, {})".format(
351 repr(self
._expr
_str
),
353 repr(self
._text
_loc
),
358 class _Cond(_Item
, _ExprMixin
):
360 self
, item
: _Item
, expr_str
: str, expr
: ast
.Expression
, text_loc
: TextLocation
362 super().__init
__(text_loc
)
363 _ExprMixin
.__init
__(self
, expr_str
, expr
)
372 return "_Cond({}, {}, {}, {})".format(
374 repr(self
._expr
_str
),
376 repr(self
._text
_loc
),
380 # Macro definition item.
381 class _MacroDef(_Item
):
383 self
, name
: str, param_names
: List
[str], group
: _Group
, text_loc
: TextLocation
385 super().__init
__(text_loc
)
387 self
._param
_names
= param_names
397 def param_names(self
):
398 return self
._param
_names
406 return "_MacroDef({}, {}, {}, {})".format(
408 repr(self
._param
_names
),
410 repr(self
._text
_loc
),
414 # Macro expansion parameter.
415 class _MacroExpParam
:
416 def __init__(self
, expr_str
: str, expr
: ast
.Expression
, text_loc
: TextLocation
):
417 self
._expr
_str
= expr_str
419 self
._text
_loc
= text_loc
424 return self
._expr
_str
431 # Source text location.
434 return self
._text
_loc
437 return "_MacroExpParam({}, {}, {})".format(
438 repr(self
._expr
_str
), repr(self
._expr
), repr(self
._text
_loc
)
442 # Macro expansion item.
443 class _MacroExp(_Item
, _RepableItem
):
447 params
: List
[_MacroExpParam
],
448 text_loc
: TextLocation
,
450 super().__init
__(text_loc
)
452 self
._params
= params
465 return "_MacroExp({}, {}, {})".format(
468 repr(self
._text
_loc
),
472 # A parsing error containing a message and a text location.
473 class ParseError(RuntimeError):
475 def _create(cls
, msg
: str, text_loc
: TextLocation
):
476 self
= cls
.__new
__(cls
)
477 self
._init
(msg
, text_loc
)
480 def __init__(self
, *args
, **kwargs
): # type: ignore
481 raise NotImplementedError
483 def _init(self
, msg
: str, text_loc
: TextLocation
):
484 super().__init
__(msg
)
485 self
._text
_loc
= text_loc
487 # Source text location.
490 return self
._text
_loc
493 # Raises a parsing error, forwarding the parameters to the constructor.
494 def _raise_error(msg
: str, text_loc
: TextLocation
) -> NoReturn
:
495 raise ParseError
._create
(msg
, text_loc
) # pyright: ignore[reportPrivateUsage]
498 # Variables dictionary type (for type hints).
499 VariablesT
= Dict
[str, Union
[int, float]]
502 # Labels dictionary type (for type hints).
503 LabelsT
= Dict
[str, int]
506 # Python name pattern.
507 _py_name_pat
= re
.compile(r
"[a-zA-Z_][a-zA-Z0-9_]*")
510 # Macro definition dictionary.
511 _MacroDefsT
= Dict
[str, _MacroDef
]
516 # The constructor accepts a Normand input. After building, use the `res`
517 # property to get the resulting main group.
519 # Builds a parser to parse the Normand input `normand`, parsing
521 def __init__(self
, normand
: str, variables
: VariablesT
, labels
: LabelsT
):
522 self
._normand
= normand
526 self
._label
_names
= set(labels
.keys())
527 self
._var
_names
= set(variables
.keys())
528 self
._macro
_defs
= {} # type: _MacroDefsT
531 # Result (main group).
538 def macro_defs(self
):
539 return self
._macro
_defs
541 # Current text location.
544 return TextLocation
._create
( # pyright: ignore[reportPrivateUsage]
545 self
._line
_no
, self
._col
_no
548 # Returns `True` if this parser is done parsing.
550 return self
._at
== len(self
._normand
)
552 # Returns `True` if this parser isn't done parsing.
553 def _isnt_done(self
):
554 return not self
._is
_done
()
556 # Raises a parse error, creating it using the message `msg` and the
557 # current text location.
558 def _raise_error(self
, msg
: str) -> NoReturn
:
559 _raise_error(msg
, self
._text
_loc
)
561 # Tries to make the pattern `pat` match the current substring,
562 # returning the match object and updating `self._at`,
563 # `self._line_no`, and `self._col_no` on success.
564 def _try_parse_pat(self
, pat
: Pattern
[str]):
565 m
= pat
.match(self
._normand
, self
._at
)
570 # Skip matched string
571 self
._at
+= len(m
.group(0))
574 self
._line
_no
+= m
.group(0).count("\n")
576 # Update column number
577 for i
in reversed(range(self
._at
)):
578 if self
._normand
[i
] == "\n" or i
== 0:
580 self
._col
_no
= self
._at
+ 1
582 self
._col
_no
= self
._at
- i
586 # Return match object
589 # Expects the pattern `pat` to match the current substring,
590 # returning the match object and updating `self._at`,
591 # `self._line_no`, and `self._col_no` on success, or raising a parse
592 # error with the message `error_msg` on error.
593 def _expect_pat(self
, pat
: Pattern
[str], error_msg
: str):
595 m
= self
._try
_parse
_pat
(pat
)
599 self
._raise
_error
(error_msg
)
601 # Return match object
604 # Pattern for _skip_ws_and_comments()
605 _ws_or_syms_or_comments_pat
= re
.compile(
606 r
"(?:[\s/\\?&:;.,+[\]_=|-]|#[^#]*?(?:\n|#))*"
609 # Skips as many whitespaces, insignificant symbol characters, and
610 # comments as possible.
611 def _skip_ws_and_comments(self
):
612 self
._try
_parse
_pat
(self
._ws
_or
_syms
_or
_comments
_pat
)
614 # Pattern for _skip_ws()
615 _ws_pat
= re
.compile(r
"\s*")
617 # Skips as many whitespaces as possible.
619 self
._try
_parse
_pat
(self
._ws
_pat
)
621 # Pattern for _try_parse_hex_byte()
622 _nibble_pat
= re
.compile(r
"[A-Fa-f0-9]")
624 # Tries to parse a hexadecimal byte, returning a byte item on
626 def _try_parse_hex_byte(self
):
627 begin_text_loc
= self
._text
_loc
629 # Match initial nibble
630 m_high
= self
._try
_parse
_pat
(self
._nibble
_pat
)
636 # Expect another nibble
637 self
._skip
_ws
_and
_comments
()
638 m_low
= self
._expect
_pat
(
639 self
._nibble
_pat
, "Expecting another hexadecimal nibble"
643 return _Byte(int(m_high
.group(0) + m_low
.group(0), 16), begin_text_loc
)
645 # Patterns for _try_parse_bin_byte()
646 _bin_byte_bit_pat
= re
.compile(r
"[01]")
647 _bin_byte_prefix_pat
= re
.compile(r
"%")
649 # Tries to parse a binary byte, returning a byte item on success.
650 def _try_parse_bin_byte(self
):
651 begin_text_loc
= self
._text
_loc
654 if self
._try
_parse
_pat
(self
._bin
_byte
_prefix
_pat
) is None:
659 bits
= [] # type: List[str]
662 self
._skip
_ws
_and
_comments
()
663 m
= self
._expect
_pat
(self
._bin
_byte
_bit
_pat
, "Expecting a bit (`0` or `1`)")
664 bits
.append(m
.group(0))
667 return _Byte(int("".join(bits
), 2), begin_text_loc
)
669 # Patterns for _try_parse_dec_byte()
670 _dec_byte_prefix_pat
= re
.compile(r
"\$")
671 _dec_byte_val_pat
= re
.compile(r
"(?P<neg>-?)(?P<val>\d+)")
673 # Tries to parse a decimal byte, returning a byte item on success.
674 def _try_parse_dec_byte(self
):
675 begin_text_loc
= self
._text
_loc
678 if self
._try
_parse
_pat
(self
._dec
_byte
_prefix
_pat
) is None:
684 m
= self
._expect
_pat
(self
._dec
_byte
_val
_pat
, "Expecting a decimal constant")
687 val
= int(m
.group("val")) * (-1 if m
.group("neg") == "-" else 1)
690 if val
< -128 or val
> 255:
691 _raise_error("Invalid decimal byte value {}".format(val
), begin_text_loc
)
697 return _Byte(val
, begin_text_loc
)
699 # Tries to parse a byte, returning a byte item on success.
700 def _try_parse_byte(self
):
702 item
= self
._try
_parse
_hex
_byte
()
708 item
= self
._try
_parse
_bin
_byte
()
714 item
= self
._try
_parse
_dec
_byte
()
719 # Patterns for _try_parse_str()
720 _str_prefix_pat
= re
.compile(r
'(?:u(?P<len>16|32)(?P<bo>be|le))?\s*"')
721 _str_suffix_pat
= re
.compile(r
'"')
722 _str_str_pat
= re
.compile(r
'(?:(?:\\.)|[^"])*')
724 # Strings corresponding to escape sequence characters
725 _str_escape_seq_strs
= {
739 # Tries to parse a string, returning a string item on success.
740 def _try_parse_str(self
):
741 begin_text_loc
= self
._text
_loc
744 m
= self
._try
_parse
_pat
(self
._str
_prefix
_pat
)
753 if m
.group("len") is not None:
754 encoding
= "utf_{}_{}".format(m
.group("len"), m
.group("bo"))
757 m
= self
._expect
_pat
(self
._str
_str
_pat
, "Expecting a literal string")
759 # Expect end of string
760 self
._expect
_pat
(self
._str
_suffix
_pat
, 'Expecting `"` (end of literal string)')
762 # Replace escape sequences
765 for ec
in '0abefnrtv"\\':
766 val
= val
.replace(r
"\{}".format(ec
), self
._str
_escape
_seq
_strs
[ec
])
769 data
= val
.encode(encoding
)
772 return _Str(data
, begin_text_loc
)
774 # Common right parenthesis pattern
775 _right_paren_pat
= re
.compile(r
"\)")
777 # Patterns for _try_parse_group()
778 _group_prefix_pat
= re
.compile(r
"\(|!g(?:roup)?\b")
780 # Tries to parse a group, returning a group item on success.
781 def _try_parse_group(self
):
782 begin_text_loc
= self
._text
_loc
785 m_open
= self
._try
_parse
_pat
(self
._group
_prefix
_pat
)
792 items
= self
._parse
_items
()
794 # Expect end of group
795 self
._skip
_ws
_and
_comments
()
797 if m_open
.group(0) == "(":
798 pat
= self
._right
_paren
_pat
801 pat
= self
._block
_end
_pat
804 self
._expect
_pat
(pat
, "Expecting an item or `{}` (end of group)".format(exp
))
807 return _Group(items
, begin_text_loc
)
809 # Returns a stripped expression string and an AST expression node
810 # from the expression string `expr_str` at text location `text_loc`.
811 def _ast_expr_from_str(self
, expr_str
: str, text_loc
: TextLocation
):
812 # Create an expression node from the expression string
813 expr_str
= expr_str
.strip().replace("\n", " ")
816 expr
= ast
.parse(expr_str
, mode
="eval")
819 "Invalid expression `{}`: invalid syntax".format(expr_str
),
823 return expr_str
, expr
825 # Patterns for _try_parse_num_and_attr()
826 _val_expr_pat
= re
.compile(r
"([^}:]+):\s*")
827 _fl_num_len_attr_pat
= re
.compile(r
"8|16|24|32|40|48|56|64")
828 _leb128_int_attr_pat
= re
.compile(r
"(u|s)leb128")
830 # Tries to parse a value and attribute (fixed length in bits or
831 # `leb128`), returning a value item on success.
832 def _try_parse_num_and_attr(self
):
833 begin_text_loc
= self
._text
_loc
836 m_expr
= self
._try
_parse
_pat
(self
._val
_expr
_pat
)
842 # Create an expression node from the expression string
843 expr_str
, expr
= self
._ast
_expr
_from
_str
(m_expr
.group(1), begin_text_loc
)
846 m_attr
= self
._try
_parse
_pat
(self
._fl
_num
_len
_attr
_pat
)
850 m_attr
= self
._try
_parse
_pat
(self
._leb
128_int
_attr
_pat
)
853 # At this point it's invalid
855 "Expecting a length (multiple of eight bits), `uleb128`, or `sleb128`"
858 # Return LEB128 integer item
859 cls
= _ULeb128Int
if m_attr
.group(1) == "u" else _SLeb128Int
860 return cls(expr_str
, expr
, begin_text_loc
)
862 # Return fixed-length number item
866 int(m_attr
.group(0)),
870 # Patterns for _try_parse_var_assign()
871 _var_assign_name_equal_pat
= re
.compile(r
"({})\s*=".format(_py_name_pat
.pattern
))
872 _var_assign_expr_pat
= re
.compile(r
"[^}]+")
874 # Tries to parse a variable assignment, returning a variable
875 # assignment item on success.
876 def _try_parse_var_assign(self
):
877 begin_text_loc
= self
._text
_loc
880 m
= self
._try
_parse
_pat
(self
._var
_assign
_name
_equal
_pat
)
889 if name
== _icitte_name
:
891 "`{}` is a reserved variable name".format(_icitte_name
), begin_text_loc
894 if name
in self
._label
_names
:
895 _raise_error("Existing label named `{}`".format(name
), begin_text_loc
)
897 # Expect an expression
899 m
= self
._expect
_pat
(self
._var
_assign
_expr
_pat
, "Expecting an expression")
901 # Create an expression node from the expression string
902 expr_str
, expr
= self
._ast
_expr
_from
_str
(m
.group(0), begin_text_loc
)
904 # Add to known variable names
905 self
._var
_names
.add(name
)
915 # Pattern for _try_parse_set_bo()
916 _bo_pat
= re
.compile(r
"[bl]e")
918 # Tries to parse a byte order name, returning a byte order setting
920 def _try_parse_set_bo(self
):
921 begin_text_loc
= self
._text
_loc
924 m
= self
._try
_parse
_pat
(self
._bo
_pat
)
930 # Return corresponding item
931 if m
.group(0) == "be":
932 return _SetBo(ByteOrder
.BE
, begin_text_loc
)
934 assert m
.group(0) == "le"
935 return _SetBo(ByteOrder
.LE
, begin_text_loc
)
937 # Patterns for _try_parse_val_or_bo()
938 _val_var_assign_set_bo_prefix_pat
= re
.compile(r
"\{")
939 _val_var_assign_set_bo_suffix_pat
= re
.compile(r
"\}")
941 # Tries to parse a value, a variable assignment, or a byte order
942 # setting, returning an item on success.
943 def _try_parse_val_or_var_assign_or_set_bo(self
):
945 if self
._try
_parse
_pat
(self
._val
_var
_assign
_set
_bo
_prefix
_pat
) is None:
951 # Variable assignment item?
952 item
= self
._try
_parse
_var
_assign
()
956 item
= self
._try
_parse
_num
_and
_attr
()
959 # Byte order setting item?
960 item
= self
._try
_parse
_set
_bo
()
963 # At this point it's invalid
965 "Expecting a fixed-length number, a variable assignment, or a byte order setting"
970 self
._expect
_pat
(self
._val
_var
_assign
_set
_bo
_suffix
_pat
, "Expecting `}`")
973 # Common constant integer patterns
974 _pos_const_int_pat
= re
.compile(r
"0[Xx][A-Fa-f0-9]+|\d+")
975 _const_int_pat
= re
.compile(r
"(?P<neg>-)?(?:{})".format(_pos_const_int_pat
.pattern
))
977 # Tries to parse an offset setting value (after the initial `<`),
978 # returning an offset item on success.
979 def _try_parse_set_offset_val(self
):
980 begin_text_loc
= self
._text
_loc
983 m
= self
._try
_parse
_pat
(self
._pos
_const
_int
_pat
)
990 return _SetOffset(int(m
.group(0), 0), begin_text_loc
)
992 # Tries to parse a label name (after the initial `<`), returning a
993 # label item on success.
994 def _try_parse_label_name(self
):
995 begin_text_loc
= self
._text
_loc
998 m
= self
._try
_parse
_pat
(_py_name_pat
)
1007 if name
== _icitte_name
:
1009 "`{}` is a reserved label name".format(_icitte_name
), begin_text_loc
1012 if name
in self
._label
_names
:
1013 _raise_error("Duplicate label name `{}`".format(name
), begin_text_loc
)
1015 if name
in self
._var
_names
:
1016 _raise_error("Existing variable named `{}`".format(name
), begin_text_loc
)
1018 # Add to known label names
1019 self
._label
_names
.add(name
)
1022 return _Label(name
, begin_text_loc
)
1024 # Patterns for _try_parse_label_or_set_offset()
1025 _label_set_offset_prefix_pat
= re
.compile(r
"<")
1026 _label_set_offset_suffix_pat
= re
.compile(r
">")
1028 # Tries to parse a label or an offset setting, returning an item on
1030 def _try_parse_label_or_set_offset(self
):
1032 if self
._try
_parse
_pat
(self
._label
_set
_offset
_prefix
_pat
) is None:
1036 # Offset setting item?
1038 item
= self
._try
_parse
_set
_offset
_val
()
1042 item
= self
._try
_parse
_label
_name
()
1045 # At this point it's invalid
1046 self
._raise
_error
("Expecting a label name or an offset setting value")
1050 self
._expect
_pat
(self
._label
_set
_offset
_suffix
_pat
, "Expecting `>`")
1053 # Patterns for _try_parse_align_offset()
1054 _align_offset_prefix_pat
= re
.compile(r
"@")
1055 _align_offset_val_pat
= re
.compile(r
"\d+")
1056 _align_offset_pad_val_prefix_pat
= re
.compile(r
"~")
1058 # Tries to parse an offset alignment, returning an offset alignment
1060 def _try_parse_align_offset(self
):
1061 begin_text_loc
= self
._text
_loc
1064 if self
._try
_parse
_pat
(self
._align
_offset
_prefix
_pat
) is None:
1070 # Expect an alignment
1071 align_text_loc
= self
._text
_loc
1072 m
= self
._expect
_pat
(
1073 self
._align
_offset
_val
_pat
,
1074 "Expecting an alignment (positive multiple of eight bits)",
1077 # Validate alignment
1078 val
= int(m
.group(0))
1080 if val
<= 0 or (val
% 8) != 0:
1082 "Invalid alignment value {} (not a positive multiple of eight)".format(
1092 if self
._try
_parse
_pat
(self
._align
_offset
_pad
_val
_prefix
_pat
) is not None:
1094 pad_val_text_loc
= self
._text
_loc
1095 m
= self
._expect
_pat
(self
._pos
_const
_int
_pat
, "Expecting a byte value")
1098 pad_val
= int(m
.group(0), 0)
1102 "Invalid padding byte value {}".format(pad_val
),
1107 return _AlignOffset(val
, pad_val
, begin_text_loc
)
1109 # Patterns for _expect_rep_mul_expr()
1110 _inner_expr_prefix_pat
= re
.compile(r
"\{")
1111 _inner_expr_pat
= re
.compile(r
"[^}]+")
1112 _inner_expr_suffix_pat
= re
.compile(r
"\}")
1114 # Parses a constant integer if `accept_const_int` is `True`
1115 # (possibly negative if `allow_neg` is `True`), a name, or an
1116 # expression within `{` and `}`.
1117 def _expect_const_int_name_expr(
1118 self
, accept_const_int
: bool, allow_neg
: bool = False
1120 expr_text_loc
= self
._text
_loc
1125 if accept_const_int
:
1126 m
= self
._try
_parse
_pat
(self
._const
_int
_pat
)
1130 m
= self
._try
_parse
_pat
(_py_name_pat
)
1134 if self
._try
_parse
_pat
(self
._inner
_expr
_prefix
_pat
) is None:
1135 pos_msg
= "" if allow_neg
else "positive "
1137 if accept_const_int
:
1138 mid_msg
= "a {}constant integer, a name, or `{{`".format(
1142 mid_msg
= "a name or `{`"
1144 # At this point it's invalid
1145 self
._raise
_error
("Expecting {}".format(mid_msg
))
1147 # Expect an expression
1149 expr_text_loc
= self
._text
_loc
1150 m
= self
._expect
_pat
(self
._inner
_expr
_pat
, "Expecting an expression")
1151 expr_str
= m
.group(0)
1155 self
._expect
_pat
(self
._inner
_expr
_suffix
_pat
, "Expecting `}`")
1157 expr_str
= m
.group(0)
1159 if m
.group("neg") == "-" and not allow_neg
:
1160 _raise_error("Expecting a positive constant integer", expr_text_loc
)
1162 expr_str
= m
.group(0)
1164 return self
._ast
_expr
_from
_str
(expr_str
, expr_text_loc
)
1166 # Parses the multiplier expression of a repetition (block or
1167 # post-item) and returns the expression string and AST node.
1168 def _expect_rep_mul_expr(self
):
1169 return self
._expect
_const
_int
_name
_expr
(True)
1171 # Common block end pattern
1172 _block_end_pat
= re
.compile(r
"!end\b")
1174 # Pattern for _try_parse_rep_block()
1175 _rep_block_prefix_pat
= re
.compile(r
"!r(?:epeat)?\b")
1177 # Tries to parse a repetition block, returning a repetition item on
1179 def _try_parse_rep_block(self
):
1180 begin_text_loc
= self
._text
_loc
1183 if self
._try
_parse
_pat
(self
._rep
_block
_prefix
_pat
) is None:
1188 self
._skip
_ws
_and
_comments
()
1189 expr_str
, expr
= self
._expect
_rep
_mul
_expr
()
1192 self
._skip
_ws
_and
_comments
()
1193 items_text_loc
= self
._text
_loc
1194 items
= self
._parse
_items
()
1196 # Expect end of block
1197 self
._skip
_ws
_and
_comments
()
1199 self
._block
_end
_pat
, "Expecting an item or `!end` (end of repetition block)"
1203 return _Rep(_Group(items
, items_text_loc
), expr_str
, expr
, begin_text_loc
)
1205 # Pattern for _try_parse_cond_block()
1206 _cond_block_prefix_pat
= re
.compile(r
"!if\b")
1208 # Tries to parse a conditional block, returning a conditional item
1210 def _try_parse_cond_block(self
):
1211 begin_text_loc
= self
._text
_loc
1214 if self
._try
_parse
_pat
(self
._cond
_block
_prefix
_pat
) is None:
1219 self
._skip
_ws
_and
_comments
()
1220 expr_str
, expr
= self
._expect
_const
_int
_name
_expr
(False)
1223 self
._skip
_ws
_and
_comments
()
1224 items_text_loc
= self
._text
_loc
1225 items
= self
._parse
_items
()
1227 # Expect end of block
1228 self
._skip
_ws
_and
_comments
()
1230 self
._block
_end
_pat
,
1231 "Expecting an item or `!end` (end of conditional block)",
1235 return _Cond(_Group(items
, items_text_loc
), expr_str
, expr
, begin_text_loc
)
1237 # Common left parenthesis pattern
1238 _left_paren_pat
= re
.compile(r
"\(")
1240 # Patterns for _try_parse_macro_def() and _try_parse_macro_exp()
1241 _macro_params_comma_pat
= re
.compile(",")
1243 # Patterns for _try_parse_macro_def()
1244 _macro_def_prefix_pat
= re
.compile(r
"!m(?:acro)?\b")
1246 # Tries to parse a macro definition, adding it to `self._macro_defs`
1247 # and returning `True` on success.
1248 def _try_parse_macro_def(self
):
1249 begin_text_loc
= self
._text
_loc
1252 if self
._try
_parse
_pat
(self
._macro
_def
_prefix
_pat
) is None:
1258 name_text_loc
= self
._text
_loc
1259 m
= self
._expect
_pat
(_py_name_pat
, "Expecting a valid macro name")
1264 if name
in self
._macro
_defs
:
1265 _raise_error("Duplicate macro named `{}`".format(name
), name_text_loc
)
1269 self
._expect
_pat
(self
._left
_paren
_pat
, "Expecting `(`")
1271 # Try to parse comma-separated parameter names
1272 param_names
= [] # type: List[str]
1273 expect_comma
= False
1279 if self
._try
_parse
_pat
(self
._right
_paren
_pat
) is not None:
1285 self
._expect
_pat
(self
._macro
_params
_comma
_pat
, "Expecting `,`")
1287 # Expect parameter name
1289 param_text_loc
= self
._text
_loc
1290 m
= self
._expect
_pat
(_py_name_pat
, "Expecting valid parameter name")
1292 if m
.group(0) in param_names
:
1294 "Duplicate macro parameter named `{}`".format(m
.group(0)),
1298 param_names
.append(m
.group(0))
1302 self
._skip
_ws
_and
_comments
()
1303 items_text_loc
= self
._text
_loc
1304 old_var_names
= self
._var
_names
.copy()
1305 old_label_names
= self
._label
_names
.copy()
1306 self
._var
_names
= set() # type: Set[str]
1307 self
._label
_names
= set() # type: Set[str]
1308 items
= self
._parse
_items
()
1309 self
._var
_names
= old_var_names
1310 self
._label
_names
= old_label_names
1314 self
._block
_end
_pat
, "Expecting an item or `!end` (end of macro block)"
1318 self
._macro
_defs
[name
] = _MacroDef(
1319 name
, param_names
, _Group(items
, items_text_loc
), begin_text_loc
1324 # Patterns for _try_parse_macro_exp()
1325 _macro_exp_prefix_pat
= re
.compile(r
"m\b")
1326 _macro_exp_colon_pat
= re
.compile(r
":")
1328 # Tries to parse a macro expansion, returning a macro expansion item
1330 def _try_parse_macro_exp(self
):
1331 begin_text_loc
= self
._text
_loc
1334 if self
._try
_parse
_pat
(self
._macro
_exp
_prefix
_pat
) is None:
1340 self
._expect
_pat
(self
._macro
_exp
_colon
_pat
, "Expecting `:`")
1342 # Expect a macro name
1344 name_text_loc
= self
._text
_loc
1345 m
= self
._expect
_pat
(_py_name_pat
, "Expecting a valid macro name")
1349 macro_def
= self
._macro
_defs
.get(name
)
1351 if macro_def
is None:
1352 _raise_error("Unknown macro name `{}`".format(name
), name_text_loc
)
1356 self
._expect
_pat
(self
._left
_paren
_pat
, "Expecting `(`")
1358 # Try to parse comma-separated parameter values
1359 params_text_loc
= self
._text
_loc
1360 params
= [] # type: List[_MacroExpParam]
1361 expect_comma
= False
1367 if self
._try
_parse
_pat
(self
._right
_paren
_pat
) is not None:
1373 self
._expect
_pat
(self
._macro
_params
_comma
_pat
, "Expecting `,`")
1376 param_text_loc
= self
._text
_loc
1379 *self
._expect
_const
_int
_name
_expr
(True, True), param_text_loc
1384 # Validate parameter values
1385 if len(params
) != len(macro_def
.param_names
):
1386 sing_plur
= "" if len(params
) == 1 else "s"
1388 "Macro expansion passes {} parameter{} while the definition expects {}".format(
1389 len(params
), sing_plur
, len(macro_def
.param_names
)
1395 return _MacroExp(name
, params
, begin_text_loc
)
1397 # Tries to parse a base item (anything except a repetition),
1398 # returning it on success.
1399 def _try_parse_base_item(self
):
1401 item
= self
._try
_parse
_byte
()
1403 if item
is not None:
1407 item
= self
._try
_parse
_str
()
1409 if item
is not None:
1412 # Value, variable assignment, or byte order setting item?
1413 item
= self
._try
_parse
_val
_or
_var
_assign
_or
_set
_bo
()
1415 if item
is not None:
1418 # Label or offset setting item?
1419 item
= self
._try
_parse
_label
_or
_set
_offset
()
1421 if item
is not None:
1424 # Offset alignment item?
1425 item
= self
._try
_parse
_align
_offset
()
1427 if item
is not None:
1431 item
= self
._try
_parse
_group
()
1433 if item
is not None:
1436 # Repetition block item?
1437 item
= self
._try
_parse
_rep
_block
()
1439 if item
is not None:
1442 # Conditional block item?
1443 item
= self
._try
_parse
_cond
_block
()
1445 if item
is not None:
1449 item
= self
._try
_parse
_macro
_exp
()
1451 if item
is not None:
1454 # Pattern for _try_parse_rep_post()
1455 _rep_post_prefix_pat
= re
.compile(r
"\*")
1457 # Tries to parse a post-item repetition, returning the expression
1458 # string and AST expression node on success.
1459 def _try_parse_rep_post(self
):
1461 if self
._try
_parse
_pat
(self
._rep
_post
_prefix
_pat
) is None:
1465 # Return expression string and AST expression
1466 self
._skip
_ws
_and
_comments
()
1467 return self
._expect
_rep
_mul
_expr
()
1469 # Tries to parse an item, possibly followed by a repetition,
1470 # returning `True` on success.
1472 # Appends any parsed item to `items`.
1473 def _try_append_item(self
, items
: List
[_Item
]):
1474 self
._skip
_ws
_and
_comments
()
1477 item
= self
._try
_parse
_base
_item
()
1482 # Parse repetition if the base item is repeatable
1483 if isinstance(item
, _RepableItem
):
1484 self
._skip
_ws
_and
_comments
()
1485 rep_text_loc
= self
._text
_loc
1486 rep_ret
= self
._try
_parse
_rep
_post
()
1488 if rep_ret
is not None:
1489 item
= _Rep(item
, *rep_ret
, rep_text_loc
)
1494 # Parses and returns items, skipping whitespaces, insignificant
1495 # symbols, and comments when allowed, and stopping at the first
1496 # unknown character.
1498 # Accepts and registers macro definitions if `accept_macro_defs`
1500 def _parse_items(self
, accept_macro_defs
: bool = False) -> List
[_Item
]:
1501 items
= [] # type: List[_Item]
1503 while self
._isnt
_done
():
1504 # Try to append item
1505 if not self
._try
_append
_item
(items
):
1506 if accept_macro_defs
and self
._try
_parse
_macro
_def
():
1509 # Unknown at this point
1514 # Parses the whole Normand input, setting `self._res` to the main
1515 # group item on success.
1517 if len(self
._normand
.strip()) == 0:
1518 # Special case to make sure there's something to consume
1519 self
._res
= _Group([], self
._text
_loc
)
1522 # Parse first level items
1523 items
= self
._parse
_items
(True)
1525 # Make sure there's nothing left
1526 self
._skip
_ws
_and
_comments
()
1528 if self
._isnt
_done
():
1530 "Unexpected character `{}`".format(self
._normand
[self
._at
])
1533 # Set main group item
1534 self
._res
= _Group(items
, self
._text
_loc
)
1537 # The return type of parse().
1543 variables
: VariablesT
,
1546 bo
: Optional
[ByteOrder
],
1548 self
= cls
.__new
__(cls
)
1549 self
._init
(data
, variables
, labels
, offset
, bo
)
1552 def __init__(self
, *args
, **kwargs
): # type: ignore
1553 raise NotImplementedError
1558 variables
: VariablesT
,
1561 bo
: Optional
[ByteOrder
],
1564 self
._vars
= variables
1565 self
._labels
= labels
1566 self
._offset
= offset
1574 # Dictionary of updated variable names to their last computed value.
1576 def variables(self
):
1579 # Dictionary of updated main group label names to their computed
1590 # Updated byte order.
1592 def byte_order(self
):
1596 # Raises a parse error for the item `item`, creating it using the
1598 def _raise_error_for_item(msg
: str, item
: _Item
) -> NoReturn
:
1599 _raise_error(msg
, item
.text_loc
)
1602 # The `ICITTE` reserved name.
1603 _icitte_name
= "ICITTE"
1606 # Base node visitor.
1608 # Calls the _visit_name() method for each name node which isn't the name
1610 class _NodeVisitor(ast
.NodeVisitor
):
1612 self
._parent
_is
_call
= False
1614 def generic_visit(self
, node
: ast
.AST
):
1615 if type(node
) is ast
.Call
:
1616 self
._parent
_is
_call
= True
1617 elif type(node
) is ast
.Name
and not self
._parent
_is
_call
:
1618 self
._visit
_name
(node
.id)
1620 super().generic_visit(node
)
1621 self
._parent
_is
_call
= False
1624 def _visit_name(self
, name
: str):
1628 # Expression validator: validates that all the names within the
1629 # expression are allowed.
1630 class _ExprValidator(_NodeVisitor
):
1631 def __init__(self
, expr_str
: str, text_loc
: TextLocation
, allowed_names
: Set
[str]):
1633 self
._expr
_str
= expr_str
1634 self
._text
_loc
= text_loc
1635 self
._allowed
_names
= allowed_names
1637 def _visit_name(self
, name
: str):
1638 # Make sure the name refers to a known and reachable
1639 # variable/label name.
1640 if name
!= _icitte_name
and name
not in self
._allowed
_names
:
1641 msg
= "Illegal (unknown or unreachable) variable/label name `{}` in expression `{}`".format(
1642 name
, self
._expr
_str
1645 allowed_names
= self
._allowed
_names
.copy()
1646 allowed_names
.add(_icitte_name
)
1648 if len(allowed_names
) > 0:
1649 allowed_names_str
= ", ".join(
1650 sorted(["`{}`".format(name
) for name
in allowed_names
])
1652 msg
+= "; the legal names are {{{}}}".format(allowed_names_str
)
1664 variables
: VariablesT
,
1667 bo
: Optional
[ByteOrder
],
1669 self
.variables
= variables
.copy()
1670 self
.labels
= labels
.copy()
1671 self
.offset
= offset
1675 return "_GenState({}, {}, {}, {})".format(
1676 repr(self
.variables
), repr(self
.labels
), repr(self
.offset
), repr(self
.bo
)
1680 # Fixed-length number item instance.
1681 class _FlNumItemInst
:
1682 def __init__(self
, item
: _FlNum
, offset_in_data
: int, state
: _GenState
):
1684 self
._offset
_in
_data
= offset_in_data
1692 def offset_in_data(self
):
1693 return self
._offset
_in
_data
1700 # Generator of data and final state from a group item.
1702 # Generation happens in memory at construction time. After building, use
1703 # the `data`, `variables`, `labels`, `offset`, and `bo` properties to
1704 # get the resulting context.
1706 # The steps of generation are:
1708 # 1. Handle each item in prefix order.
1710 # The handlers append bytes to `self._data` and update some current
1711 # state object (`_GenState` instance).
1713 # When handling a fixed-length number item, try to evaluate its
1714 # expression using the current state. If this fails, then it might be
1715 # because the expression refers to a "future" label: save the current
1716 # offset in `self._data` (generated data) and a snapshot of the
1717 # current state within `self._fl_num_item_insts` (`_FlNumItemInst`
1718 # object). _gen_fl_num_item_insts() will deal with this later.
1720 # When handling the items of a group, keep a map of immediate label
1721 # names to their offset. Then, after having processed all the items,
1722 # update the relevant saved state snapshots in
1723 # `self._fl_num_item_insts` with those immediate label values.
1724 # _gen_fl_num_item_insts() will deal with this later.
1726 # 2. Handle all the fixed-length number item instances of which the
1727 # expression evaluation failed before.
1729 # At this point, `self._fl_num_item_insts` contains everything that's
1730 # needed to evaluate the expressions, including the values of
1731 # "future" labels from the point of view of some fixed-length number
1734 # If an evaluation fails at this point, then it's a user error.
1739 macro_defs
: _MacroDefsT
,
1740 variables
: VariablesT
,
1743 bo
: Optional
[ByteOrder
],
1745 self
._macro
_defs
= macro_defs
1746 self
._fl
_num
_item
_insts
= [] # type: List[_FlNumItemInst]
1747 self
._gen
(group
, _GenState(variables
, labels
, offset
, bo
))
1754 # Updated variables.
1756 def variables(self
):
1757 return self
._final
_state
.variables
1759 # Updated main group labels.
1762 return self
._final
_state
.labels
1767 return self
._final
_state
.offset
1769 # Updated byte order.
1772 return self
._final
_state
.bo
1774 # Evaluates the expression `expr` of which the original string is
1775 # `expr_str` at the location `text_loc` considering the current
1776 # generation state `state`.
1778 # If `allow_float` is `True`, then the type of the result may be
1783 expr
: ast
.Expression
,
1784 text_loc
: TextLocation
,
1786 allow_float
: bool = False,
1788 syms
= {} # type: VariablesT
1789 syms
.update(state
.labels
)
1791 # Set the `ICITTE` name to the current offset
1792 syms
[_icitte_name
] = state
.offset
1794 # Add the current variables
1795 syms
.update(state
.variables
)
1797 # Validate the node and its children
1798 _ExprValidator(expr_str
, text_loc
, set(syms
.keys())).visit(expr
)
1800 # Compile and evaluate expression node
1802 val
= eval(compile(expr
, "", "eval"), None, syms
)
1803 except Exception as exc
:
1805 "Failed to evaluate expression `{}`: {}".format(expr_str
, exc
),
1809 # Convert `bool` result type to `int` to normalize
1810 if type(val
) is bool:
1813 # Validate result type
1814 expected_types
= {int}
# type: Set[type]
1818 expected_types
.add(float)
1819 type_msg
+= " or `float`"
1821 if type(val
) not in expected_types
:
1823 "Invalid expression `{}`: expecting result type {}, not `{}`".format(
1824 expr_str
, type_msg
, type(val
).__name
__
1831 # Evaluates the expression of `item` considering the current
1832 # generation state `state`.
1834 # If `allow_float` is `True`, then the type of the result may be
1837 def _eval_item_expr(
1838 item
: Union
[_FlNum
, _Leb128Int
, _VarAssign
, _Rep
, _Cond
],
1840 allow_float
: bool = False,
1842 return _Gen
._eval
_expr
(
1843 item
.expr_str
, item
.expr
, item
.text_loc
, state
, allow_float
1846 # Handles the byte item `item`.
1847 def _handle_byte_item(self
, item
: _Byte
, state
: _GenState
):
1848 self
._data
.append(item
.val
)
1849 state
.offset
+= item
.size
1851 # Handles the string item `item`.
1852 def _handle_str_item(self
, item
: _Str
, state
: _GenState
):
1853 self
._data
+= item
.data
1854 state
.offset
+= item
.size
1856 # Handles the byte order setting item `item`.
1857 def _handle_set_bo_item(self
, item
: _SetBo
, state
: _GenState
):
1858 # Update current byte order
1861 # Handles the variable assignment item `item`.
1862 def _handle_var_assign_item(self
, item
: _VarAssign
, state
: _GenState
):
1864 state
.variables
[item
.name
] = self
._eval
_item
_expr
(item
, state
, True)
1866 # Handles the fixed-length number item `item`.
1867 def _handle_fl_num_item(self
, item
: _FlNum
, state
: _GenState
):
1868 # Validate current byte order
1869 if state
.bo
is None and item
.len > 8:
1870 _raise_error_for_item(
1871 "Current byte order isn't defined at first fixed-length number (`{}`) to encode on more than 8 bits".format(
1877 # Try an immediate evaluation. If it fails, then keep everything
1878 # needed to (try to) generate the bytes of this item later.
1880 data
= self
._gen
_fl
_num
_item
_inst
_data
(item
, state
)
1882 self
._fl
_num
_item
_insts
.append(
1883 _FlNumItemInst(item
, len(self
._data
), copy
.deepcopy(state
))
1886 # Reserve space in `self._data` for this instance
1887 data
= bytes([0] * (item
.len // 8))
1893 state
.offset
+= len(data
)
1895 # Returns the size, in bytes, required to encode the value `val`
1896 # with LEB128 (signed version if `is_signed` is `True`).
1898 def _leb128_size_for_val(val
: int, is_signed
: bool):
1900 # Equivalent upper bound.
1902 # For example, if `val` is -128, then the full integer for
1903 # this number of bits would be [-128, 127].
1906 # Number of bits (add one for the sign if needed)
1907 bits
= val
.bit_length() + int(is_signed
)
1912 # Seven bits per byte
1913 return math
.ceil(bits
/ 7)
1915 # Handles the LEB128 integer item `item`.
1916 def _handle_leb128_int_item(self
, item
: _Leb128Int
, state
: _GenState
):
1918 val
= self
._eval
_item
_expr
(item
, state
, False)
1921 size
= self
._leb
128_size
_for
_val
(val
, type(item
) is _SLeb128Int
)
1924 for _
in range(size
):
1925 # Seven LSBs, MSB of the byte set (continue)
1926 self
._data
.append((val
& 0x7F) |
0x80)
1929 # Clear MSB of last byte (stop)
1930 self
._data
[-1] &= ~
0x80
1933 state
.offset
+= size
1935 # Handles the group item `item`, removing the immediate labels from
1936 # `state` at the end if `remove_immediate_labels` is `True`.
1937 def _handle_group_item(
1938 self
, item
: _Group
, state
: _GenState
, remove_immediate_labels
: bool = True
1940 first_fl_num_item_inst_index
= len(self
._fl
_num
_item
_insts
)
1941 immediate_labels
= {} # type: LabelsT
1944 for subitem
in item
.items
:
1945 if type(subitem
) is _Label
:
1946 # Add to local immediate labels
1947 immediate_labels
[subitem
.name
] = state
.offset
1949 self
._handle
_item
(subitem
, state
)
1951 # Remove immediate labels from current state if needed
1952 if remove_immediate_labels
:
1953 for name
in immediate_labels
:
1954 del state
.labels
[name
]
1956 # Add all immediate labels to all state snapshots since
1957 # `first_fl_num_item_inst_index`.
1958 for inst
in self
._fl
_num
_item
_insts
[first_fl_num_item_inst_index
:]:
1959 inst
.state
.labels
.update(immediate_labels
)
1961 # Handles the repetition item `item`.
1962 def _handle_rep_item(self
, item
: _Rep
, state
: _GenState
):
1963 # Compute the repetition count
1964 mul
= _Gen
._eval
_item
_expr
(item
, state
)
1968 _raise_error_for_item(
1969 "Invalid expression `{}`: unexpected negative result {:,}".format(
1975 # Generate item data `mul` times
1976 for _
in range(mul
):
1977 self
._handle
_item
(item
.item
, state
)
1979 # Handles the conditional item `item`.
1980 def _handle_cond_item(self
, item
: _Rep
, state
: _GenState
):
1981 # Compute the conditional value
1982 val
= _Gen
._eval
_item
_expr
(item
, state
)
1984 # Generate item data if needed
1986 self
._handle
_item
(item
.item
, state
)
1988 # Evaluates the parameters of the macro expansion item `item`
1989 # considering the initial state `init_state` and returns a new state
1990 # to handle the items of the macro.
1991 def _eval_macro_exp_params(self
, item
: _MacroExp
, init_state
: _GenState
):
1993 exp_state
= _GenState({}, {}, init_state
.offset
, init_state
.bo
)
1995 # Evaluate the parameter expressions
1996 macro_def
= self
._macro
_defs
[item
.name
]
1998 for param_name
, param
in zip(macro_def
.param_names
, item
.params
):
1999 exp_state
.variables
[param_name
] = _Gen
._eval
_expr
(
2000 param
.expr_str
, param
.expr
, param
.text_loc
, init_state
, True
2005 # Handles the macro expansion item `item`.
2006 def _handle_macro_exp_item(self
, item
: _MacroExp
, state
: _GenState
):
2008 exp_state
= self
._eval
_macro
_exp
_params
(item
, state
)
2010 # Process the contained group
2011 init_data_size
= len(self
._data
)
2012 self
._handle
_item
(self
._macro
_defs
[item
.name
].group
, exp_state
)
2014 # Update state offset and return
2015 state
.offset
+= len(self
._data
) - init_data_size
2017 # Handles the offset setting item `item`.
2018 def _handle_set_offset_item(self
, item
: _SetOffset
, state
: _GenState
):
2019 state
.offset
= item
.val
2021 # Handles offset alignment item `item` (adds padding).
2022 def _handle_align_offset_item(self
, item
: _AlignOffset
, state
: _GenState
):
2023 init_offset
= state
.offset
2024 align_bytes
= item
.val
// 8
2025 state
.offset
= (state
.offset
+ align_bytes
- 1) // align_bytes
* align_bytes
2026 self
._data
+= bytes([item
.pad_val
] * (state
.offset
- init_offset
))
2028 # Handles the label item `item`.
2029 def _handle_label_item(self
, item
: _Label
, state
: _GenState
):
2030 state
.labels
[item
.name
] = state
.offset
2032 # Handles the item `item`, returning the updated next repetition
2034 def _handle_item(self
, item
: _Item
, state
: _GenState
):
2035 return self
._item
_handlers
[type(item
)](item
, state
)
2037 # Generates the data for a fixed-length integer item instance having
2038 # the value `val` and returns it.
2039 def _gen_fl_int_item_inst_data(self
, val
: int, item
: _FlNum
, state
: _GenState
):
2041 if val
< -(2 ** (item
.len - 1)) or val
> 2**item
.len - 1:
2042 _raise_error_for_item(
2043 "Value {:,} is outside the {}-bit range when evaluating expression `{}`".format(
2044 val
, item
.len, item
.expr_str
2049 # Encode result on 64 bits (to extend the sign bit whatever the
2050 # value of `item.len`).
2053 ">" if state
.bo
in (None, ByteOrder
.BE
) else "<",
2054 "Q" if val
>= 0 else "q",
2059 # Keep only the requested length
2060 len_bytes
= item
.len // 8
2062 if state
.bo
in (None, ByteOrder
.BE
):
2063 # Big endian: keep last bytes
2064 data
= data
[-len_bytes
:]
2066 # Little endian: keep first bytes
2067 assert state
.bo
== ByteOrder
.LE
2068 data
= data
[:len_bytes
]
2073 # Generates the data for a fixed-length floating point number item
2074 # instance having the value `val` and returns it.
2075 def _gen_fl_float_item_inst_data(self
, val
: float, item
: _FlNum
, state
: _GenState
):
2077 if item
.len not in (32, 64):
2078 _raise_error_for_item(
2079 "Invalid {}-bit length for a fixed-length floating point number (value {:,})".format(
2085 # Encode and return result
2088 ">" if state
.bo
in (None, ByteOrder
.BE
) else "<",
2089 "f" if item
.len == 32 else "d",
2094 # Generates the data for a fixed-length number item instance and
2096 def _gen_fl_num_item_inst_data(self
, item
: _FlNum
, state
: _GenState
):
2098 val
= self
._eval
_item
_expr
(item
, state
, True)
2100 # Handle depending on type
2101 if type(val
) is int:
2102 return self
._gen
_fl
_int
_item
_inst
_data
(val
, item
, state
)
2104 assert type(val
) is float
2105 return self
._gen
_fl
_float
_item
_inst
_data
(val
, item
, state
)
2107 # Generates the data for all the fixed-length number item instances
2108 # and writes it at the correct offset within `self._data`.
2109 def _gen_fl_num_item_insts(self
):
2110 for inst
in self
._fl
_num
_item
_insts
:
2112 data
= self
._gen
_fl
_num
_item
_inst
_data
(inst
.item
, inst
.state
)
2114 # Insert bytes into `self._data`
2115 self
._data
[inst
.offset_in_data
: inst
.offset_in_data
+ len(data
)] = data
2117 # Generates the data (`self._data`) and final state
2118 # (`self._final_state`) from `group` and the initial state `state`.
2119 def _gen(self
, group
: _Group
, state
: _GenState
):
2121 self
._data
= bytearray()
2124 self
._item
_handlers
= {
2125 _AlignOffset
: self
._handle
_align
_offset
_item
,
2126 _Byte
: self
._handle
_byte
_item
,
2127 _Cond
: self
._handle
_cond
_item
,
2128 _FlNum
: self
._handle
_fl
_num
_item
,
2129 _Group
: self
._handle
_group
_item
,
2130 _Label
: self
._handle
_label
_item
,
2131 _MacroExp
: self
._handle
_macro
_exp
_item
,
2132 _Rep
: self
._handle
_rep
_item
,
2133 _SetBo
: self
._handle
_set
_bo
_item
,
2134 _SetOffset
: self
._handle
_set
_offset
_item
,
2135 _SLeb128Int
: self
._handle
_leb
128_int
_item
,
2136 _Str
: self
._handle
_str
_item
,
2137 _ULeb128Int
: self
._handle
_leb
128_int
_item
,
2138 _VarAssign
: self
._handle
_var
_assign
_item
,
2139 } # type: Dict[type, Callable[[Any, _GenState], None]]
2141 # Handle the group item, _not_ removing the immediate labels
2142 # because the `labels` property offers them.
2143 self
._handle
_group
_item
(group
, state
, False)
2145 # This is actually the final state
2146 self
._final
_state
= state
2148 # Generate all the fixed-length number bytes now that we know
2150 self
._gen
_fl
_num
_item
_insts
()
2153 # Returns a `ParseResult` instance containing the bytes encoded by the
2154 # input string `normand`.
2156 # `init_variables` is a dictionary of initial variable names (valid
2157 # Python names) to integral values. A variable name must not be the
2158 # reserved name `ICITTE`.
2160 # `init_labels` is a dictionary of initial label names (valid Python
2161 # names) to integral values. A label name must not be the reserved name
2164 # `init_offset` is the initial offset.
2166 # `init_byte_order` is the initial byte order.
2168 # Raises `ParseError` on any parsing error.
2171 init_variables
: Optional
[VariablesT
] = None,
2172 init_labels
: Optional
[LabelsT
] = None,
2173 init_offset
: int = 0,
2174 init_byte_order
: Optional
[ByteOrder
] = None,
2176 if init_variables
is None:
2179 if init_labels
is None:
2182 parser
= _Parser(normand
, init_variables
, init_labels
)
2191 return ParseResult
._create
( # pyright: ignore[reportPrivateUsage]
2192 gen
.data
, gen
.variables
, gen
.labels
, gen
.offset
, gen
.bo
2196 # Parses the command-line arguments.
2197 def _parse_cli_args():
2201 ap
= argparse
.ArgumentParser()
2208 help="initial offset (positive)",
2214 choices
=["be", "le"],
2216 help="initial byte order (`be` or `le`)",
2222 help="add an initial variable (may be repeated)",
2229 help="add an initial label (may be repeated)",
2232 "--version", action
="version", version
="Normand {}".format(__version__
)
2239 help="input path (none means standard input)",
2243 return ap
.parse_args()
2246 # Raises a command-line error with the message `msg`.
2247 def _raise_cli_error(msg
: str) -> NoReturn
:
2248 raise RuntimeError("Command-line error: {}".format(msg
))
2251 # Returns a dictionary of string to integers from the list of strings
2252 # `args` containing `NAME=VAL` entries.
2253 def _dict_from_arg(args
: Optional
[List
[str]]):
2254 d
= {} # type: LabelsT
2260 m
= re
.match(r
"({})=(\d+)$".format(_py_name_pat
.pattern
), arg
)
2263 _raise_cli_error("Invalid assignment {}".format(arg
))
2265 d
[m
.group(1)] = int(m
.group(2))
2270 # CLI entry point without exception handling.
2275 args
= _parse_cli_args()
2278 if args
.path
is None:
2279 normand
= sys
.stdin
.read()
2281 with
open(args
.path
) as f
:
2284 # Variables and labels
2285 variables
= typing
.cast(VariablesT
, _dict_from_arg(args
.var
))
2286 labels
= _dict_from_arg(args
.label
)
2290 _raise_cli_error("Invalid negative offset {}")
2292 # Validate and set byte order
2293 bo
= None # type: Optional[ByteOrder]
2295 if args
.byte_order
is not None:
2296 if args
.byte_order
== "be":
2299 assert args
.byte_order
== "le"
2304 res
= parse(normand
, variables
, labels
, args
.offset
, bo
)
2305 except ParseError
as exc
:
2308 if args
.path
is not None:
2309 prefix
= "{}:".format(os
.path
.abspath(args
.path
))
2312 "{}{}:{} - {}".format(
2313 prefix
, exc
.text_loc
.line_no
, exc
.text_loc
.col_no
, str(exc
)
2318 sys
.stdout
.buffer.write(res
.data
)
2321 # Prints the exception message `msg` and exits with status 1.
2322 def _fail(msg
: str) -> NoReturn
:
2323 if not msg
.endswith("."):
2326 print(msg
, file=sys
.stderr
)
2334 except Exception as exc
:
2338 if __name__
== "__main__":