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 __author__
= "Philippe Proulx"
43 from typing
import Any
, Dict
, List
, Union
, Pattern
, Callable
, NoReturn
, Optional
46 # Text location (line and column numbers).
49 def _create(cls
, line_no
: int, col_no
: int):
50 self
= cls
.__new
__(cls
)
51 self
._init
(line_no
, col_no
)
54 def __init__(*args
, **kwargs
): # type: ignore
55 raise NotImplementedError
57 def _init(self
, line_no
: int, col_no
: int):
58 self
._line
_no
= line_no
74 def __init__(self
, text_loc
: TextLoc
):
75 self
._text
_loc
= text_loc
77 # Source text location.
82 # Returns the size, in bytes, of this item.
85 def size(self
) -> int:
90 class _RepableItem(_Item
):
95 class _Byte(_RepableItem
):
96 def __init__(self
, val
: int, text_loc
: TextLoc
):
97 super().__init
__(text_loc
)
110 return "_Byte({}, {})".format(hex(self
._val
), self
._text
_loc
)
114 class _Str(_RepableItem
):
115 def __init__(self
, data
: bytes
, text_loc
: TextLoc
):
116 super().__init
__(text_loc
)
126 return len(self
._data
)
129 return "_Str({}, {})".format(repr(self
._data
), self
._text
_loc
)
134 class ByteOrder(enum
.Enum
):
144 def __init__(self
, bo
: ByteOrder
, text_loc
: TextLoc
):
145 super().__init
__(text_loc
)
159 def __init__(self
, name
: str, text_loc
: TextLoc
):
160 super().__init
__(text_loc
)
173 return "_Label({}, {})".format(repr(self
._name
), self
._text
_loc
)
177 class _Offset(_Item
):
178 def __init__(self
, val
: int, text_loc
: TextLoc
):
179 super().__init
__(text_loc
)
192 return "_Offset({}, {})".format(repr(self
._val
), self
._text
_loc
)
195 # Mixin of containing an AST expression and its string.
197 def __init__(self
, expr_str
: str, expr
: ast
.Expression
):
198 self
._expr
_str
= expr_str
204 return self
._expr
_str
206 # Expression node to evaluate.
213 class _Var(_Item
, _ExprMixin
):
215 self
, name
: str, expr_str
: str, expr
: ast
.Expression
, text_loc
: TextLoc
217 super().__init
__(text_loc
)
218 _ExprMixin
.__init
__(self
, expr_str
, expr
)
231 return "_Var({}, {}, {}, {})".format(
232 repr(self
._name
), repr(self
._expr
_str
), repr(self
._expr
), self
._text
_loc
236 # Value, possibly needing more than one byte.
237 class _Val(_RepableItem
, _ExprMixin
):
239 self
, expr_str
: str, expr
: ast
.Expression
, len: int, text_loc
: TextLoc
241 super().__init
__(text_loc
)
242 _ExprMixin
.__init
__(self
, expr_str
, expr
)
252 return self
._len
// 8
255 return "_Val({}, {}, {}, {})".format(
256 repr(self
._expr
_str
), repr(self
._expr
), repr(self
._len
), self
._text
_loc
260 # Expression item type.
261 _ExprItemT
= Union
[_Val
, _Var
]
265 class _Group(_RepableItem
):
266 def __init__(self
, items
: List
[_Item
], text_loc
: TextLoc
):
267 super().__init
__(text_loc
)
269 self
._size
= sum([item
.size
for item
in self
._items
])
281 return "_Group({}, {})".format(repr(self
._items
), self
._text
_loc
)
286 def __init__(self
, item
: _RepableItem
, mul
: int, text_loc
: TextLoc
):
287 super().__init
__(text_loc
)
296 # Repetition multiplier.
303 return self
._item
.size
* self
._mul
306 return "_Rep({}, {}, {})".format(
307 repr(self
._item
), repr(self
._mul
), self
._text
_loc
311 # A parsing error containing a message and a text location.
312 class ParseError(RuntimeError):
314 def _create(cls
, msg
: str, text_loc
: TextLoc
):
315 self
= cls
.__new
__(cls
)
316 self
._init
(msg
, text_loc
)
319 def __init__(self
, *args
, **kwargs
): # type: ignore
320 raise NotImplementedError
322 def _init(self
, msg
: str, text_loc
: TextLoc
):
323 super().__init
__(msg
)
324 self
._text
_loc
= text_loc
326 # Source text location.
329 return self
._text
_loc
332 # Raises a parsing error, forwarding the parameters to the constructor.
333 def _raise_error(msg
: str, text_loc
: TextLoc
) -> NoReturn
:
334 raise ParseError
._create
(msg
, text_loc
) # pyright: ignore[reportPrivateUsage]
337 # Variable (and label) dictionary type.
338 VarsT
= Dict
[str, int]
341 # Python name pattern.
342 _py_name_pat
= re
.compile(r
"[a-zA-Z_][a-zA-Z0-9_]*")
347 # The constructor accepts a Normand input. After building, use the `res`
348 # property to get the resulting main group.
350 # Builds a parser to parse the Normand input `normand`, parsing
352 def __init__(self
, normand
: str, variables
: VarsT
, labels
: VarsT
):
353 self
._normand
= normand
357 self
._label
_names
= set(labels
.keys())
358 self
._var
_names
= set(variables
.keys())
361 # Result (main group).
366 # Current text location.
369 return TextLoc
._create
( # pyright: ignore[reportPrivateUsage]
370 self
._line
_no
, self
._col
_no
373 # Returns `True` if this parser is done parsing.
375 return self
._at
== len(self
._normand
)
377 # Returns `True` if this parser isn't done parsing.
378 def _isnt_done(self
):
379 return not self
._is
_done
()
381 # Raises a parse error, creating it using the message `msg` and the
382 # current text location.
383 def _raise_error(self
, msg
: str) -> NoReturn
:
384 _raise_error(msg
, self
._text
_loc
)
386 # Tries to make the pattern `pat` match the current substring,
387 # returning the match object and updating `self._at`,
388 # `self._line_no`, and `self._col_no` on success.
389 def _try_parse_pat(self
, pat
: Pattern
[str]):
390 m
= pat
.match(self
._normand
, self
._at
)
395 # Skip matched string
396 self
._at
+= len(m
.group(0))
399 self
._line
_no
+= m
.group(0).count("\n")
401 # Update column number
402 for i
in reversed(range(self
._at
)):
403 if self
._normand
[i
] == "\n" or i
== 0:
405 self
._col
_no
= self
._at
+ 1
407 self
._col
_no
= self
._at
- i
411 # Return match object
414 # Expects the pattern `pat` to match the current substring,
415 # returning the match object and updating `self._at`,
416 # `self._line_no`, and `self._col_no` on success, or raising a parse
417 # error with the message `error_msg` on error.
418 def _expect_pat(self
, pat
: Pattern
[str], error_msg
: str):
420 m
= self
._try
_parse
_pat
(pat
)
424 self
._raise
_error
(error_msg
)
426 # Return match object
429 # Pattern for _skip_ws_and_comments()
430 _ws_or_syms_or_comments_pat
= re
.compile(
431 r
"(?:[\s!@/\\?&:;.,+[\]_=|-]|#[^#]*?(?:\n|#))*"
434 # Skips as many whitespaces, insignificant symbol characters, and
435 # comments as possible.
436 def _skip_ws_and_comments(self
):
437 self
._try
_parse
_pat
(self
._ws
_or
_syms
_or
_comments
_pat
)
439 # Pattern for _try_parse_hex_byte()
440 _nibble_pat
= re
.compile(r
"[A-Fa-f0-9]")
442 # Tries to parse a hexadecimal byte, returning a byte item on
444 def _try_parse_hex_byte(self
):
445 begin_text_loc
= self
._text
_loc
447 # Match initial nibble
448 m_high
= self
._try
_parse
_pat
(self
._nibble
_pat
)
454 # Expect another nibble
455 self
._skip
_ws
_and
_comments
()
456 m_low
= self
._expect
_pat
(
457 self
._nibble
_pat
, "Expecting another hexadecimal nibble"
461 return _Byte(int(m_high
.group(0) + m_low
.group(0), 16), begin_text_loc
)
463 # Patterns for _try_parse_bin_byte()
464 _bin_byte_bit_pat
= re
.compile(r
"[01]")
465 _bin_byte_prefix_pat
= re
.compile(r
"%")
467 # Tries to parse a binary byte, returning a byte item on success.
468 def _try_parse_bin_byte(self
):
469 begin_text_loc
= self
._text
_loc
472 if self
._try
_parse
_pat
(self
._bin
_byte
_prefix
_pat
) is None:
477 bits
= [] # type: List[str]
480 self
._skip
_ws
_and
_comments
()
481 m
= self
._expect
_pat
(self
._bin
_byte
_bit
_pat
, "Expecting a bit (`0` or `1`)")
482 bits
.append(m
.group(0))
485 return _Byte(int("".join(bits
), 2), begin_text_loc
)
487 # Patterns for _try_parse_dec_byte()
488 _dec_byte_prefix_pat
= re
.compile(r
"\$\s*")
489 _dec_byte_val_pat
= re
.compile(r
"(?P<neg>-?)(?P<val>\d+)")
491 # Tries to parse a decimal byte, returning a byte item on success.
492 def _try_parse_dec_byte(self
):
493 begin_text_loc
= self
._text
_loc
496 if self
._try
_parse
_pat
(self
._dec
_byte
_prefix
_pat
) is None:
501 m
= self
._expect
_pat
(self
._dec
_byte
_val
_pat
, "Expecting a decimal constant")
504 val
= int(m
.group("val")) * (-1 if m
.group("neg") == "-" else 1)
507 if val
< -128 or val
> 255:
508 _raise_error("Invalid decimal byte value {}".format(val
), begin_text_loc
)
514 return _Byte(val
, begin_text_loc
)
516 # Tries to parse a byte, returning a byte item on success.
517 def _try_parse_byte(self
):
519 item
= self
._try
_parse
_hex
_byte
()
525 item
= self
._try
_parse
_bin
_byte
()
531 item
= self
._try
_parse
_dec
_byte
()
536 # Patterns for _try_parse_str()
537 _str_prefix_pat
= re
.compile(r
'(?:u(?P<len>16|32)(?P<bo>be|le))?\s*"')
538 _str_suffix_pat
= re
.compile(r
'"')
539 _str_str_pat
= re
.compile(r
'(?:(?:\\.)|[^"])*')
541 # Strings corresponding to escape sequence characters
542 _str_escape_seq_strs
= {
556 # Tries to parse a string, returning a string item on success.
557 def _try_parse_str(self
):
558 begin_text_loc
= self
._text
_loc
561 m
= self
._try
_parse
_pat
(self
._str
_prefix
_pat
)
570 if m
.group("len") is not None:
571 encoding
= "utf_{}_{}".format(m
.group("len"), m
.group("bo"))
574 m
= self
._expect
_pat
(self
._str
_str
_pat
, "Expecting a literal string")
576 # Expect end of string
577 self
._expect
_pat
(self
._str
_suffix
_pat
, 'Expecting `"` (end of literal string)')
579 # Replace escape sequences
582 for ec
in '0abefnrtv"\\':
583 val
= val
.replace(r
"\{}".format(ec
), self
._str
_escape
_seq
_strs
[ec
])
586 data
= val
.encode(encoding
)
589 return _Str(data
, begin_text_loc
)
591 # Patterns for _try_parse_group()
592 _group_prefix_pat
= re
.compile(r
"\(")
593 _group_suffix_pat
= re
.compile(r
"\)")
595 # Tries to parse a group, returning a group item on success.
596 def _try_parse_group(self
):
597 begin_text_loc
= self
._text
_loc
600 if self
._try
_parse
_pat
(self
._group
_prefix
_pat
) is None:
605 items
= self
._parse
_items
()
607 # Expect end of group
608 self
._skip
_ws
_and
_comments
()
610 self
._group
_suffix
_pat
, "Expecting an item or `)` (end of group)"
614 return _Group(items
, begin_text_loc
)
616 # Returns a stripped expression string and an AST expression node
617 # from the expression string `expr_str` at text location `text_loc`.
618 def _ast_expr_from_str(self
, expr_str
: str, text_loc
: TextLoc
):
619 # Create an expression node from the expression string
620 expr_str
= expr_str
.strip().replace("\n", " ")
623 expr
= ast
.parse(expr_str
, mode
="eval")
626 "Invalid expression `{}`: invalid syntax".format(expr_str
),
630 return expr_str
, expr
632 # Patterns for _try_parse_val_and_len()
633 _val_expr_pat
= re
.compile(r
"([^}:]+):")
634 _val_len_pat
= re
.compile(r
"\s*(8|16|24|32|40|48|56|64)")
636 # Tries to parse a value and length, returning a value item on
638 def _try_parse_val_and_len(self
):
639 begin_text_loc
= self
._text
_loc
642 m_expr
= self
._try
_parse
_pat
(self
._val
_expr
_pat
)
649 m_len
= self
._expect
_pat
(
650 self
._val
_len
_pat
, "Expecting a length (multiple of eight bits)"
653 # Create an expression node from the expression string
654 expr_str
, expr
= self
._ast
_expr
_from
_str
(m_expr
.group(1), begin_text_loc
)
664 # Patterns for _try_parse_val_and_len()
665 _var_pat
= re
.compile(
666 r
"(?P<name>{})\s*=\s*(?P<expr>[^}}]+)".format(_py_name_pat
.pattern
)
669 # Tries to parse a variable, returning a variable item on success.
670 def _try_parse_var(self
):
671 begin_text_loc
= self
._text
_loc
674 m
= self
._try
_parse
_pat
(self
._var
_pat
)
681 name
= m
.group("name")
683 if name
== _icitte_name
:
685 "`{}` is a reserved variable name".format(_icitte_name
), begin_text_loc
688 if name
in self
._label
_names
:
689 _raise_error("Existing label named `{}`".format(name
), begin_text_loc
)
691 # Add to known variable names
692 self
._var
_names
.add(name
)
694 # Create an expression node from the expression string
695 expr_str
, expr
= self
._ast
_expr
_from
_str
(m
.group("expr"), begin_text_loc
)
705 # Pattern for _try_parse_bo_name()
706 _bo_pat
= re
.compile(r
"[bl]e")
708 # Tries to parse a byte order name, returning a byte order item on
710 def _try_parse_bo_name(self
):
711 begin_text_loc
= self
._text
_loc
714 m
= self
._try
_parse
_pat
(self
._bo
_pat
)
720 # Return corresponding item
721 if m
.group(0) == "be":
722 return _Bo(ByteOrder
.BE
, begin_text_loc
)
724 assert m
.group(0) == "le"
725 return _Bo(ByteOrder
.LE
, begin_text_loc
)
727 # Patterns for _try_parse_val_or_bo()
728 _val_var_bo_prefix_pat
= re
.compile(r
"\{\s*")
729 _val_var_bo_suffix_pat
= re
.compile(r
"\s*}")
731 # Tries to parse a value, a variable, or a byte order, returning an
733 def _try_parse_val_or_var_or_bo(self
):
735 if self
._try
_parse
_pat
(self
._val
_var
_bo
_prefix
_pat
) is None:
740 item
= self
._try
_parse
_var
()
744 item
= self
._try
_parse
_val
_and
_len
()
748 item
= self
._try
_parse
_bo
_name
()
751 # At this point it's invalid
752 self
._raise
_error
("Expecting a value, a variable, or a byte order")
755 self
._expect
_pat
(self
._val
_var
_bo
_suffix
_pat
, "Expecting `}`")
758 # Pattern for _try_parse_offset_val() and _try_parse_rep()
759 _pos_const_int_pat
= re
.compile(r
"0[Xx][A-Fa-f0-9]+|\d+")
761 # Tries to parse an offset value (after the initial `<`), returning
762 # an offset item on success.
763 def _try_parse_offset_val(self
):
764 begin_text_loc
= self
._text
_loc
767 m
= self
._try
_parse
_pat
(self
._pos
_const
_int
_pat
)
774 return _Offset(int(m
.group(0), 0), begin_text_loc
)
776 # Tries to parse a label name (after the initial `<`), returning a
777 # label item on success.
778 def _try_parse_label_name(self
):
779 begin_text_loc
= self
._text
_loc
782 m
= self
._try
_parse
_pat
(_py_name_pat
)
791 if name
== _icitte_name
:
793 "`{}` is a reserved label name".format(_icitte_name
), begin_text_loc
796 if name
in self
._label
_names
:
797 _raise_error("Duplicate label name `{}`".format(name
), begin_text_loc
)
799 if name
in self
._var
_names
:
800 _raise_error("Existing variable named `{}`".format(name
), begin_text_loc
)
802 # Add to known label names
803 self
._label
_names
.add(name
)
806 return _Label(name
, begin_text_loc
)
808 # Patterns for _try_parse_label_or_offset()
809 _label_offset_prefix_pat
= re
.compile(r
"<\s*")
810 _label_offset_suffix_pat
= re
.compile(r
"\s*>")
812 # Tries to parse a label or an offset, returning an item on success.
813 def _try_parse_label_or_offset(self
):
815 if self
._try
_parse
_pat
(self
._label
_offset
_prefix
_pat
) is None:
820 item
= self
._try
_parse
_offset
_val
()
824 item
= self
._try
_parse
_label
_name
()
827 # At this point it's invalid
828 self
._raise
_error
("Expecting a label name or an offset value")
831 self
._expect
_pat
(self
._label
_offset
_suffix
_pat
, "Expecting `>`")
834 # Tries to parse a base item (anything except a repetition),
835 # returning it on success.
836 def _try_parse_base_item(self
):
838 item
= self
._try
_parse
_byte
()
844 item
= self
._try
_parse
_str
()
849 # Value, variable, or byte order item?
850 item
= self
._try
_parse
_val
_or
_var
_or
_bo
()
855 # Label or offset item?
856 item
= self
._try
_parse
_label
_or
_offset
()
862 item
= self
._try
_parse
_group
()
867 # Pattern for _try_parse_rep()
868 _rep_prefix_pat
= re
.compile(r
"\*\s*")
870 # Tries to parse a repetition, returning the multiplier on success,
872 def _try_parse_rep(self
):
874 if self
._try
_parse
_pat
(self
._rep
_prefix
_pat
) is None:
878 # Expect and return a decimal multiplier
879 self
._skip
_ws
_and
_comments
()
880 m
= self
._expect
_pat
(
881 self
._pos
_const
_int
_pat
, "Expecting a positive integral multiplier"
883 return int(m
.group(0), 0)
885 # Tries to parse an item, possibly followed by a repetition,
886 # returning `True` on success.
888 # Appends any parsed item to `items`.
889 def _try_append_item(self
, items
: List
[_Item
]):
890 self
._skip
_ws
_and
_comments
()
893 item
= self
._try
_parse
_base
_item
()
899 # Parse repetition if the base item is repeatable
900 if isinstance(item
, _RepableItem
):
901 self
._skip
_ws
_and
_comments
()
902 rep_text_loc
= self
._text
_loc
903 rep
= self
._try
_parse
_rep
()
906 # No item, but that's okay
909 # Convert to repetition item
910 item
= _Rep(item
, rep
, rep_text_loc
)
915 # Parses and returns items, skipping whitespaces, insignificant
916 # symbols, and comments when allowed, and stopping at the first
918 def _parse_items(self
) -> List
[_Item
]:
919 items
= [] # type: List[_Item]
921 while self
._isnt
_done
():
923 if not self
._try
_append
_item
(items
):
924 # Unknown at this point
929 # Parses the whole Normand input, setting `self._res` to the main
930 # group item on success.
932 if len(self
._normand
.strip()) == 0:
933 # Special case to make sure there's something to consume
934 self
._res
= _Group([], self
._text
_loc
)
937 # Parse first level items
938 items
= self
._parse
_items
()
940 # Make sure there's nothing left
941 self
._skip
_ws
_and
_comments
()
943 if self
._isnt
_done
():
945 "Unexpected character `{}`".format(self
._normand
[self
._at
])
948 # Set main group item
949 self
._res
= _Group(items
, self
._text
_loc
)
952 # The return type of parse().
961 bo
: Optional
[ByteOrder
],
963 self
= cls
.__new
__(cls
)
964 self
._init
(data
, variables
, labels
, offset
, bo
)
967 def __init__(self
, *args
, **kwargs
): # type: ignore
968 raise NotImplementedError
976 bo
: Optional
[ByteOrder
],
979 self
._vars
= variables
980 self
._labels
= labels
981 self
._offset
= offset
989 # Dictionary of updated variable names to their last computed value.
994 # Dictionary of updated main group label names to their computed
1005 # Updated byte order.
1007 def byte_order(self
):
1011 # Raises a parse error for the item `item`, creating it using the
1013 def _raise_error_for_item(msg
: str, item
: _Item
) -> NoReturn
:
1014 _raise_error(msg
, item
.text_loc
)
1017 # The `ICITTE` reserved name.
1018 _icitte_name
= "ICITTE"
1021 # Value expression validator.
1022 class _ExprValidator(ast
.NodeVisitor
):
1023 def __init__(self
, item
: _ExprItemT
, syms
: VarsT
):
1026 self
._parent
_is
_call
= False
1028 def generic_visit(self
, node
: ast
.AST
):
1029 if type(node
) is ast
.Call
:
1030 self
._parent
_is
_call
= True
1031 elif type(node
) is ast
.Name
and not self
._parent
_is
_call
:
1032 # Make sure the name refers to a known label name
1033 if node
.id != _icitte_name
and node
.id not in self
._syms
:
1035 "Unknown variable/label name `{}` in expression `{}`".format(
1036 node
.id, self
._item
.expr_str
1038 self
._item
.text_loc
,
1041 # TODO: Restrict the set of allowed node types
1043 super().generic_visit(node
)
1044 self
._parent
_is
_call
= False
1047 # Keeper of labels for a given group instance.
1049 # A group instance is one iteration of a given group.
1050 class _GroupInstanceLabels
:
1052 self
._instance
_labels
= {} # type: Dict[_Group, Dict[int, VarsT]]
1054 # Assigns the labels `labels` to a new instance of `group`.
1055 def add(self
, group
: _Group
, labels
: VarsT
):
1056 if group
not in self
._instance
_labels
:
1057 self
._instance
_labels
[group
] = {}
1059 spec_instance_labels
= self
._instance
_labels
[group
]
1060 spec_instance_labels
[len(spec_instance_labels
)] = labels
.copy()
1062 # Returns the labels (not a copy) of the instance `instance_index`
1063 # of the group `group`.
1064 def labels(self
, group
: _Group
, instance_index
: int):
1065 return self
._instance
_labels
[group
][instance_index
]
1068 # Generator of data and labels from a group item.
1070 # Generation happens in memory at construction time. After building, use
1071 # the `data`, `variables`, `labels`, `offset`, and `bo` properties to
1072 # get the resulting context.
1080 bo
: Optional
[ByteOrder
],
1082 self
._group
_instance
_labels
= _GroupInstanceLabels()
1083 self
._resolve
_labels
(group
, offset
, labels
.copy())
1084 self
._vars
= variables
.copy()
1085 self
._offset
= offset
1087 self
._main
_group
= group
1095 # Updated variables.
1097 def variables(self
):
1100 # Updated main group labels.
1103 return self
._group
_instance
_labels
.labels(self
._main
_group
, 0)
1110 # Updated byte order.
1115 # Fills `self._group_instance_labels` with the labels for each group
1116 # instance in `item`, starting at current offset `offset` with the
1117 # current labels `labels`.
1119 # Returns the new current offset.
1120 def _resolve_labels(self
, item
: _Item
, offset
: int, labels
: VarsT
) -> int:
1121 if type(item
) is _Group
:
1122 # First pass: compute immediate labels of this instance
1123 group_labels
= labels
.copy()
1124 group_offset
= offset
1126 for subitem
in item
.items
:
1127 if type(subitem
) is _Offset
:
1128 group_offset
= subitem
.val
1129 elif type(subitem
) is _Label
:
1130 assert subitem
.name
not in group_labels
1131 group_labels
[subitem
.name
] = group_offset
1133 group_offset
+= subitem
.size
1135 # Add to group instance labels
1136 self
._group
_instance
_labels
.add(item
, group_labels
)
1138 # Second pass: handle each item
1139 for subitem
in item
.items
:
1140 offset
= self
._resolve
_labels
(subitem
, offset
, group_labels
)
1141 elif type(item
) is _Rep
:
1142 for _
in range(item
.mul
):
1143 offset
= self
._resolve
_labels
(item
.item
, offset
, labels
)
1144 elif type(item
) is _Offset
:
1151 def _handle_byte_item(self
, item
: _Byte
):
1152 self
._data
.append(item
.val
)
1153 self
._offset
+= item
.size
1155 def _handle_str_item(self
, item
: _Str
):
1156 self
._data
+= item
.data
1157 self
._offset
+= item
.size
1159 def _handle_bo_item(self
, item
: _Bo
):
1162 def _eval_expr(self
, item
: _ExprItemT
):
1163 # Get the labels of the current group instance as the initial
1164 # symbols (copied because we're adding stuff).
1165 assert self
._cur
_group
is not None
1166 syms
= self
._group
_instance
_labels
.labels(
1167 self
._cur
_group
, self
._group
_instance
_indexes
[self
._cur
_group
]
1170 # Set the `ICITTE` name to the current offset (before encoding)
1171 syms
[_icitte_name
] = self
._offset
1173 # Add the current variables
1174 syms
.update(self
._vars
)
1176 # Validate the node and its children
1177 _ExprValidator(item
, syms
).visit(item
.expr
)
1179 # Compile and evaluate expression node
1181 val
= eval(compile(item
.expr
, "", "eval"), None, syms
)
1182 except Exception as exc
:
1183 _raise_error_for_item(
1184 "Failed to evaluate expression `{}`: {}".format(item
.expr_str
, exc
),
1189 if type(val
) is not int:
1190 _raise_error_for_item(
1191 "Invalid expression `{}`: unexpected result type `{}`".format(
1192 item
.expr_str
, type(val
).__name
__
1199 def _handle_var_item(self
, item
: _Var
):
1201 self
._vars
[item
.name
] = self
._eval
_expr
(item
)
1203 def _handle_val_item(self
, item
: _Val
):
1205 val
= self
._eval
_expr
(item
)
1208 if val
< -(2 ** (item
.len - 1)) or val
> 2**item
.len - 1:
1209 _raise_error_for_item(
1210 "Value {:,} is outside the {}-bit range when evaluating expression `{}` at byte offset {:,}".format(
1211 val
, item
.len, item
.expr_str
, self
._offset
1216 # Encode result on 64 bits (to extend the sign bit whatever the
1217 # value of `item.len`).
1218 if self
._bo
is None and item
.len > 8:
1219 _raise_error_for_item(
1220 "Current byte order isn't defined at first value (`{}`) to encode on more than 8 bits".format(
1228 ">" if self
._bo
in (None, ByteOrder
.BE
) else "<",
1229 "Q" if val
>= 0 else "q",
1234 # Keep only the requested length
1235 len_bytes
= item
.len // 8
1237 if self
._bo
in (None, ByteOrder
.BE
):
1238 # Big endian: keep last bytes
1239 data
= data
[-len_bytes
:]
1241 # Little endian: keep first bytes
1242 assert self
._bo
== ByteOrder
.LE
1243 data
= data
[:len_bytes
]
1245 # Append to current bytes and update offset
1247 self
._offset
+= len(data
)
1249 def _handle_group_item(self
, item
: _Group
):
1250 # Update the instance index of `item`
1251 if item
not in self
._group
_instance
_indexes
:
1252 self
._group
_instance
_indexes
[item
] = 0
1254 self
._group
_instance
_indexes
[item
] += 1
1256 # Changed current group
1257 old_cur_group
= self
._cur
_group
1258 self
._cur
_group
= item
1261 for subitem
in item
.items
:
1262 self
._handle
_item
(subitem
)
1264 # Restore current group
1265 self
._cur
_group
= old_cur_group
1267 def _handle_rep_item(self
, item
: _Rep
):
1268 for _
in range(item
.mul
):
1269 self
._handle
_item
(item
.item
)
1271 def _handle_offset_item(self
, item
: _Offset
):
1272 self
._offset
= item
.val
1274 def _handle_item(self
, item
: _Item
):
1275 if type(item
) in self
._item
_handlers
:
1276 self
._item
_handlers
[type(item
)](item
)
1280 self
._data
= bytearray()
1281 self
._group
_instance
_indexes
= {} # type: Dict[_Group, int]
1282 self
._cur
_group
= None
1285 self
._item
_handlers
= {
1286 _Byte
: self
._handle
_byte
_item
,
1287 _Str
: self
._handle
_str
_item
,
1288 _Bo
: self
._handle
_bo
_item
,
1289 _Val
: self
._handle
_val
_item
,
1290 _Var
: self
._handle
_var
_item
,
1291 _Group
: self
._handle
_group
_item
,
1292 _Rep
: self
._handle
_rep
_item
,
1293 _Offset
: self
._handle
_offset
_item
,
1294 } # type: Dict[type, Callable[[Any], None]]
1296 # Handle the group item
1297 self
._handle
_item
(self
._main
_group
)
1300 # Returns a `ParseResult` instance containing the bytes encoded by the
1301 # input string `normand`.
1303 # `init_variables` is a dictionary of initial variable names (valid
1304 # Python names) to integral values. A variable name must not be the
1305 # reserved name `ICITTE`.
1307 # `init_labels` is a dictionary of initial label names (valid Python
1308 # names) to integral values. A label name must not be the reserved name
1311 # `init_offset` is the initial offset.
1313 # `init_byte_order` is the initial byte order.
1315 # Raises `ParseError` on any parsing error.
1318 init_variables
: Optional
[VarsT
] = None,
1319 init_labels
: Optional
[VarsT
] = None,
1320 init_offset
: int = 0,
1321 init_byte_order
: Optional
[ByteOrder
] = None,
1323 if init_variables
is None:
1326 if init_labels
is None:
1330 _Parser(normand
, init_variables
, init_labels
).res
,
1336 return ParseResult
._create
( # pyright: ignore[reportPrivateUsage]
1337 gen
.data
, gen
.variables
, gen
.labels
, gen
.offset
, gen
.bo
1341 # Parses the command-line arguments.
1342 def _parse_cli_args():
1346 ap
= argparse
.ArgumentParser()
1353 help="initial offset (positive)",
1359 choices
=["be", "le"],
1361 help="initial byte order (`be` or `le`)",
1367 help="add an initial variable (may be repeated)",
1374 help="add an initial label (may be repeated)",
1377 "--version", action
="version", version
="Normand {}".format(__version__
)
1384 help="input path (none means standard input)",
1388 return ap
.parse_args()
1391 # Raises a command-line error with the message `msg`.
1392 def _raise_cli_error(msg
: str) -> NoReturn
:
1393 raise RuntimeError("Command-line error: {}".format(msg
))
1396 # Returns a dictionary of string to integers from the list of strings
1397 # `args` containing `NAME=VAL` entries.
1398 def _dict_from_arg(args
: Optional
[List
[str]]):
1399 d
= {} # type: Dict[str, int]
1405 m
= re
.match(r
"({})=(\d+)$".format(_py_name_pat
.pattern
), arg
)
1408 _raise_cli_error("Invalid assignment {}".format(arg
))
1413 # CLI entry point without exception handling.
1418 args
= _parse_cli_args()
1421 if args
.path
is None:
1422 normand
= sys
.stdin
.read()
1424 with
open(args
.path
) as f
:
1427 # Variables and labels
1428 variables
= _dict_from_arg(args
.var
)
1429 labels
= _dict_from_arg(args
.label
)
1433 _raise_cli_error("Invalid negative offset {}")
1435 # Validate and set byte order
1436 bo
= None # type: Optional[ByteOrder]
1438 if args
.byte_order
is not None:
1439 if args
.byte_order
== "be":
1442 assert args
.byte_order
== "le"
1447 res
= parse(normand
, variables
, labels
, args
.offset
, bo
)
1448 except ParseError
as exc
:
1451 if args
.path
is not None:
1452 prefix
= "{}:".format(os
.path
.abspath(args
.path
))
1455 "{}{}:{} - {}".format(
1456 prefix
, exc
.text_loc
.line_no
, exc
.text_loc
.col_no
, str(exc
)
1461 sys
.stdout
.buffer.write(res
.data
)
1464 # Prints the exception message `msg` and exits with status 1.
1465 def _fail(msg
: str) -> NoReturn
:
1466 if not msg
.endswith("."):
1469 print(msg
, file=sys
.stderr
)
1477 except Exception as exc
:
1481 if __name__
== "__main__":