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
):
158 def __init__(self
, name
: str, text_loc
: TextLoc
):
159 super().__init
__(text_loc
)
172 return "_Label({}, {})".format(repr(self
._name
), self
._text
_loc
)
176 class _Offset(_Item
):
177 def __init__(self
, val
: int, text_loc
: TextLoc
):
178 super().__init
__(text_loc
)
191 return "_Offset({}, {})".format(repr(self
._val
), self
._text
_loc
)
194 # Mixin of containing an AST expression and its string.
196 def __init__(self
, expr_str
: str, expr
: ast
.Expression
):
197 self
._expr
_str
= expr_str
203 return self
._expr
_str
205 # Expression node to evaluate.
212 class _Var(_Item
, _ExprMixin
):
214 self
, name
: str, expr_str
: str, expr
: ast
.Expression
, text_loc
: TextLoc
216 super().__init
__(text_loc
)
217 _ExprMixin
.__init
__(self
, expr_str
, expr
)
230 return "_Var({}, {}, {}, {})".format(
231 repr(self
._name
), repr(self
._expr
_str
), repr(self
._expr
), self
._text
_loc
235 # Value, possibly needing more than one byte.
236 class _Val(_RepableItem
, _ExprMixin
):
238 self
, expr_str
: str, expr
: ast
.Expression
, len: int, text_loc
: TextLoc
240 super().__init
__(text_loc
)
241 _ExprMixin
.__init
__(self
, expr_str
, expr
)
251 return self
._len
// 8
254 return "_Val({}, {}, {}, {})".format(
255 repr(self
._expr
_str
), repr(self
._expr
), repr(self
._len
), self
._text
_loc
259 # Expression item type.
260 _ExprItemT
= Union
[_Val
, _Var
]
264 class _Group(_RepableItem
):
265 def __init__(self
, items
: List
[_Item
], text_loc
: TextLoc
):
266 super().__init
__(text_loc
)
268 self
._size
= sum([item
.size
for item
in self
._items
])
280 return "_Group({}, {})".format(repr(self
._items
), self
._text
_loc
)
285 def __init__(self
, item
: _RepableItem
, mul
: int, text_loc
: TextLoc
):
286 super().__init
__(text_loc
)
295 # Repetition multiplier.
302 return self
._item
.size
* self
._mul
305 return "_Rep({}, {}, {})".format(
306 repr(self
._item
), repr(self
._mul
), self
._text
_loc
310 # A parsing error containing a message and a text location.
311 class ParseError(RuntimeError):
313 def _create(cls
, msg
: str, text_loc
: TextLoc
):
314 self
= cls
.__new
__(cls
)
315 self
._init
(msg
, text_loc
)
318 def __init__(self
, *args
, **kwargs
): # type: ignore
319 raise NotImplementedError
321 def _init(self
, msg
: str, text_loc
: TextLoc
):
322 super().__init
__(msg
)
323 self
._text
_loc
= text_loc
325 # Source text location.
328 return self
._text
_loc
331 # Raises a parsing error, forwarding the parameters to the constructor.
332 def _raise_error(msg
: str, text_loc
: TextLoc
) -> NoReturn
:
333 raise ParseError
._create
(msg
, text_loc
) # pyright: ignore[reportPrivateUsage]
336 # Variable (and label) dictionary type.
337 VarsT
= Dict
[str, int]
340 # Python name pattern.
341 _py_name_pat
= re
.compile(r
"[a-zA-Z_][a-zA-Z0-9_]*")
346 # The constructor accepts a Normand input. After building, use the `res`
347 # property to get the resulting main group.
349 # Builds a parser to parse the Normand input `normand`, parsing
351 def __init__(self
, normand
: str, variables
: VarsT
, labels
: VarsT
):
352 self
._normand
= normand
356 self
._label
_names
= set(labels
.keys())
357 self
._var
_names
= set(variables
.keys())
360 # Result (main group).
365 # Current text location.
368 return TextLoc
._create
( # pyright: ignore[reportPrivateUsage]
369 self
._line
_no
, self
._col
_no
372 # Returns `True` if this parser is done parsing.
374 return self
._at
== len(self
._normand
)
376 # Returns `True` if this parser isn't done parsing.
377 def _isnt_done(self
):
378 return not self
._is
_done
()
380 # Raises a parse error, creating it using the message `msg` and the
381 # current text location.
382 def _raise_error(self
, msg
: str) -> NoReturn
:
383 _raise_error(msg
, self
._text
_loc
)
385 # Tries to make the pattern `pat` match the current substring,
386 # returning the match object and updating `self._at`,
387 # `self._line_no`, and `self._col_no` on success.
388 def _try_parse_pat(self
, pat
: Pattern
[str]):
389 m
= pat
.match(self
._normand
, self
._at
)
394 # Skip matched string
395 self
._at
+= len(m
.group(0))
398 self
._line
_no
+= m
.group(0).count("\n")
400 # Update column number
401 for i
in reversed(range(self
._at
)):
402 if self
._normand
[i
] == "\n" or i
== 0:
404 self
._col
_no
= self
._at
+ 1
406 self
._col
_no
= self
._at
- i
410 # Return match object
413 # Expects the pattern `pat` to match the current substring,
414 # returning the match object and updating `self._at`,
415 # `self._line_no`, and `self._col_no` on success, or raising a parse
416 # error with the message `error_msg` on error.
417 def _expect_pat(self
, pat
: Pattern
[str], error_msg
: str):
419 m
= self
._try
_parse
_pat
(pat
)
423 self
._raise
_error
(error_msg
)
425 # Return match object
428 # Pattern for _skip_ws_and_comments()
429 _ws_or_syms_or_comments_pat
= re
.compile(
430 r
"(?:[\s!@/\\?&:;.,+[\]_=|-]|#[^#]*?(?:\n|#))*"
433 # Skips as many whitespaces, insignificant symbol characters, and
434 # comments as possible.
435 def _skip_ws_and_comments(self
):
436 self
._try
_parse
_pat
(self
._ws
_or
_syms
_or
_comments
_pat
)
438 # Pattern for _try_parse_hex_byte()
439 _nibble_pat
= re
.compile(r
"[A-Fa-f0-9]")
441 # Tries to parse a hexadecimal byte, returning a byte item on
443 def _try_parse_hex_byte(self
):
444 # Match initial nibble
445 m_high
= self
._try
_parse
_pat
(self
._nibble
_pat
)
451 # Expect another nibble
452 self
._skip
_ws
_and
_comments
()
453 m_low
= self
._expect
_pat
(
454 self
._nibble
_pat
, "Expecting another hexadecimal nibble"
458 return _Byte(int(m_high
.group(0) + m_low
.group(0), 16), self
._text
_loc
)
460 # Patterns for _try_parse_bin_byte()
461 _bin_byte_bit_pat
= re
.compile(r
"[01]")
462 _bin_byte_prefix_pat
= re
.compile(r
"%")
464 # Tries to parse a binary byte, returning a byte item on success.
465 def _try_parse_bin_byte(self
):
467 if self
._try
_parse
_pat
(self
._bin
_byte
_prefix
_pat
) is None:
472 bits
= [] # type: List[str]
475 self
._skip
_ws
_and
_comments
()
476 m
= self
._expect
_pat
(self
._bin
_byte
_bit
_pat
, "Expecting a bit (`0` or `1`)")
477 bits
.append(m
.group(0))
480 return _Byte(int("".join(bits
), 2), self
._text
_loc
)
482 # Patterns for _try_parse_dec_byte()
483 _dec_byte_prefix_pat
= re
.compile(r
"\$\s*")
484 _dec_byte_val_pat
= re
.compile(r
"(?P<neg>-?)(?P<val>\d+)")
486 # Tries to parse a decimal byte, returning a byte item on success.
487 def _try_parse_dec_byte(self
):
489 if self
._try
_parse
_pat
(self
._dec
_byte
_prefix
_pat
) is None:
494 m
= self
._expect
_pat
(self
._dec
_byte
_val
_pat
, "Expecting a decimal constant")
497 val
= int(m
.group("val")) * (-1 if m
.group("neg") == "-" else 1)
500 if val
< -128 or val
> 255:
501 self
._raise
_error
("Invalid decimal byte value {}".format(val
))
507 return _Byte(val
, self
._text
_loc
)
509 # Tries to parse a byte, returning a byte item on success.
510 def _try_parse_byte(self
):
512 item
= self
._try
_parse
_hex
_byte
()
518 item
= self
._try
_parse
_bin
_byte
()
524 item
= self
._try
_parse
_dec
_byte
()
529 # Patterns for _try_parse_str()
530 _str_prefix_pat
= re
.compile(r
'(?:u(?P<len>16|32)(?P<bo>be|le))?\s*"')
531 _str_suffix_pat
= re
.compile(r
'"')
532 _str_str_pat
= re
.compile(r
'(?:(?:\\.)|[^"])*')
534 # Strings corresponding to escape sequence characters
535 _str_escape_seq_strs
= {
549 # Tries to parse a string, returning a string item on success.
550 def _try_parse_str(self
):
552 m
= self
._try
_parse
_pat
(self
._str
_prefix
_pat
)
561 if m
.group("len") is not None:
562 encoding
= "utf_{}_{}".format(m
.group("len"), m
.group("bo"))
565 m
= self
._expect
_pat
(self
._str
_str
_pat
, "Expecting a literal string")
567 # Expect end of string
568 self
._expect
_pat
(self
._str
_suffix
_pat
, 'Expecting `"` (end of literal string)')
570 # Replace escape sequences
573 for ec
in '0abefnrtv"\\':
574 val
= val
.replace(r
"\{}".format(ec
), self
._str
_escape
_seq
_strs
[ec
])
577 data
= val
.encode(encoding
)
580 return _Str(data
, self
._text
_loc
)
582 # Patterns for _try_parse_group()
583 _group_prefix_pat
= re
.compile(r
"\(")
584 _group_suffix_pat
= re
.compile(r
"\)")
586 # Tries to parse a group, returning a group item on success.
587 def _try_parse_group(self
):
589 if self
._try
_parse
_pat
(self
._group
_prefix
_pat
) is None:
594 items
= self
._parse
_items
()
596 # Expect end of group
597 self
._skip
_ws
_and
_comments
()
599 self
._group
_suffix
_pat
, "Expecting an item or `)` (end of group)"
603 return _Group(items
, self
._text
_loc
)
605 # Returns a stripped expression string and an AST expression node
606 # from the expression string `expr_str` at text location `text_loc`.
607 def _ast_expr_from_str(self
, expr_str
: str, text_loc
: TextLoc
):
608 # Create an expression node from the expression string
609 expr_str
= expr_str
.strip().replace("\n", " ")
612 expr
= ast
.parse(expr_str
, mode
="eval")
615 "Invalid expression `{}`: invalid syntax".format(expr_str
),
619 return expr_str
, expr
621 # Patterns for _try_parse_val_and_len()
622 _val_expr_pat
= re
.compile(r
"([^}:]+):")
623 _val_len_pat
= re
.compile(r
"\s*(8|16|24|32|40|48|56|64)")
625 # Tries to parse a value and length, returning a value item on
627 def _try_parse_val_and_len(self
):
628 begin_text_loc
= self
._text
_loc
631 m_expr
= self
._try
_parse
_pat
(self
._val
_expr
_pat
)
638 m_len
= self
._expect
_pat
(
639 self
._val
_len
_pat
, "Expecting a length (multiple of eight bits)"
642 # Create an expression node from the expression string
643 expr_str
, expr
= self
._ast
_expr
_from
_str
(m_expr
.group(1), begin_text_loc
)
653 # Patterns for _try_parse_val_and_len()
654 _var_pat
= re
.compile(
655 r
"(?P<name>{})\s*=\s*(?P<expr>[^}}]+)".format(_py_name_pat
.pattern
)
658 # Tries to parse a variable, returning a variable item on success.
659 def _try_parse_var(self
):
660 begin_text_loc
= self
._text
_loc
663 m
= self
._try
_parse
_pat
(self
._var
_pat
)
670 name
= m
.group("name")
672 if name
== _icitte_name
:
673 self
._raise
_error
("`{}` is a reserved variable name".format(_icitte_name
))
675 if name
in self
._label
_names
:
676 self
._raise
_error
("Existing label named `{}`".format(name
))
678 # Add to known variable names
679 self
._var
_names
.add(name
)
681 # Create an expression node from the expression string
682 expr_str
, expr
= self
._ast
_expr
_from
_str
(m
.group("expr"), begin_text_loc
)
692 # Pattern for _try_parse_bo_name()
693 _bo_pat
= re
.compile(r
"[bl]e")
695 # Tries to parse a byte order name, returning a byte order item on
697 def _try_parse_bo_name(self
):
699 m
= self
._try
_parse
_pat
(self
._bo
_pat
)
705 # Return corresponding item
706 if m
.group(0) == "be":
707 return _Bo(ByteOrder
.BE
)
709 assert m
.group(0) == "le"
710 return _Bo(ByteOrder
.LE
)
712 # Patterns for _try_parse_val_or_bo()
713 _val_var_bo_prefix_pat
= re
.compile(r
"\{\s*")
714 _val_var_bo_suffix_pat
= re
.compile(r
"\s*}")
716 # Tries to parse a value, a variable, or a byte order, returning an
718 def _try_parse_val_or_var_or_bo(self
):
720 if self
._try
_parse
_pat
(self
._val
_var
_bo
_prefix
_pat
) is None:
725 item
= self
._try
_parse
_var
()
729 item
= self
._try
_parse
_val
_and
_len
()
733 item
= self
._try
_parse
_bo
_name
()
736 # At this point it's invalid
737 self
._raise
_error
("Expecting a value, a variable, or a byte order")
740 self
._expect
_pat
(self
._val
_var
_bo
_suffix
_pat
, "Expecting `}`")
743 # Pattern for _try_parse_offset_val() and _try_parse_rep()
744 _pos_const_int_pat
= re
.compile(r
"0[Xx][A-Fa-f0-9]+|\d+")
746 # Tries to parse an offset value (after the initial `<`), returning
747 # an offset item on success.
748 def _try_parse_offset_val(self
):
750 m
= self
._try
_parse
_pat
(self
._pos
_const
_int
_pat
)
757 return _Offset(int(m
.group(0), 0), self
._text
_loc
)
759 # Tries to parse a label name (after the initial `<`), returning a
760 # label item on success.
761 def _try_parse_label_name(self
):
763 m
= self
._try
_parse
_pat
(_py_name_pat
)
772 if name
== _icitte_name
:
773 self
._raise
_error
("`{}` is a reserved label name".format(_icitte_name
))
775 if name
in self
._label
_names
:
776 self
._raise
_error
("Duplicate label name `{}`".format(name
))
778 if name
in self
._var
_names
:
779 self
._raise
_error
("Existing variable named `{}`".format(name
))
781 # Add to known label names
782 self
._label
_names
.add(name
)
785 return _Label(name
, self
._text
_loc
)
787 # Patterns for _try_parse_label_or_offset()
788 _label_offset_prefix_pat
= re
.compile(r
"<\s*")
789 _label_offset_suffix_pat
= re
.compile(r
"\s*>")
791 # Tries to parse a label or an offset, returning an item on success.
792 def _try_parse_label_or_offset(self
):
794 if self
._try
_parse
_pat
(self
._label
_offset
_prefix
_pat
) is None:
799 item
= self
._try
_parse
_offset
_val
()
803 item
= self
._try
_parse
_label
_name
()
806 # At this point it's invalid
807 self
._raise
_error
("Expecting a label name or an offset value")
810 self
._expect
_pat
(self
._label
_offset
_suffix
_pat
, "Expecting `>`")
813 # Tries to parse a base item (anything except a repetition),
814 # returning it on success.
815 def _try_parse_base_item(self
):
817 item
= self
._try
_parse
_byte
()
823 item
= self
._try
_parse
_str
()
828 # Value, variable, or byte order item?
829 item
= self
._try
_parse
_val
_or
_var
_or
_bo
()
834 # Label or offset item?
835 item
= self
._try
_parse
_label
_or
_offset
()
841 item
= self
._try
_parse
_group
()
846 # Pattern for _try_parse_rep()
847 _rep_prefix_pat
= re
.compile(r
"\*\s*")
849 # Tries to parse a repetition, returning the multiplier on success,
851 def _try_parse_rep(self
):
852 self
._skip
_ws
_and
_comments
()
855 if self
._try
_parse
_pat
(self
._rep
_prefix
_pat
) is None:
859 # Expect and return a decimal multiplier
860 self
._skip
_ws
_and
_comments
()
861 m
= self
._expect
_pat
(
862 self
._pos
_const
_int
_pat
, "Expecting a positive integral multiplier"
864 return int(m
.group(0), 0)
866 # Tries to parse an item, possibly followed by a repetition,
867 # returning `True` on success.
869 # Appends any parsed item to `items`.
870 def _try_append_item(self
, items
: List
[_Item
]):
871 self
._skip
_ws
_and
_comments
()
874 item
= self
._try
_parse
_base
_item
()
880 # Parse repetition if the base item is repeatable
881 if isinstance(item
, _RepableItem
):
882 rep
= self
._try
_parse
_rep
()
885 # No item, but that's okay
888 # Convert to repetition item
889 item
= _Rep(item
, rep
, self
._text
_loc
)
894 # Parses and returns items, skipping whitespaces, insignificant
895 # symbols, and comments when allowed, and stopping at the first
897 def _parse_items(self
) -> List
[_Item
]:
898 items
= [] # type: List[_Item]
900 while self
._isnt
_done
():
902 if not self
._try
_append
_item
(items
):
903 # Unknown at this point
908 # Parses the whole Normand input, setting `self._res` to the main
909 # group item on success.
911 if len(self
._normand
.strip()) == 0:
912 # Special case to make sure there's something to consume
913 self
._res
= _Group([], self
._text
_loc
)
916 # Parse first level items
917 items
= self
._parse
_items
()
919 # Make sure there's nothing left
920 self
._skip
_ws
_and
_comments
()
922 if self
._isnt
_done
():
924 "Unexpected character `{}`".format(self
._normand
[self
._at
])
927 # Set main group item
928 self
._res
= _Group(items
, self
._text
_loc
)
931 # The return type of parse().
940 bo
: Optional
[ByteOrder
],
942 self
= cls
.__new
__(cls
)
943 self
._init
(data
, variables
, labels
, offset
, bo
)
946 def __init__(self
, *args
, **kwargs
): # type: ignore
947 raise NotImplementedError
955 bo
: Optional
[ByteOrder
],
958 self
._vars
= variables
959 self
._labels
= labels
960 self
._offset
= offset
968 # Dictionary of updated variable names to their last computed value.
973 # Dictionary of updated main group label names to their computed
984 # Updated byte order.
986 def byte_order(self
):
990 # Raises a parse error for the item `item`, creating it using the
992 def _raise_error_for_item(msg
: str, item
: _Item
) -> NoReturn
:
993 _raise_error(msg
, item
.text_loc
)
996 # The `ICITTE` reserved name.
997 _icitte_name
= "ICITTE"
1000 # Value expression validator.
1001 class _ExprValidator(ast
.NodeVisitor
):
1002 def __init__(self
, item
: _ExprItemT
, syms
: VarsT
):
1005 self
._parent
_is
_call
= False
1007 def generic_visit(self
, node
: ast
.AST
):
1008 if type(node
) is ast
.Call
:
1009 self
._parent
_is
_call
= True
1010 elif type(node
) is ast
.Name
and not self
._parent
_is
_call
:
1011 # Make sure the name refers to a known label name
1012 if node
.id != _icitte_name
and node
.id not in self
._syms
:
1014 "Unknown variable/label name `{}` in expression `{}`".format(
1015 node
.id, self
._item
.expr_str
1017 self
._item
.text_loc
,
1020 # TODO: Restrict the set of allowed node types
1022 super().generic_visit(node
)
1023 self
._parent
_is
_call
= False
1026 # Keeper of labels for a given group instance.
1028 # A group instance is one iteration of a given group.
1029 class _GroupInstanceLabels
:
1031 self
._instance
_labels
= {} # type: Dict[_Group, Dict[int, VarsT]]
1033 # Assigns the labels `labels` to a new instance of `group`.
1034 def add(self
, group
: _Group
, labels
: VarsT
):
1035 if group
not in self
._instance
_labels
:
1036 self
._instance
_labels
[group
] = {}
1038 spec_instance_labels
= self
._instance
_labels
[group
]
1039 spec_instance_labels
[len(spec_instance_labels
)] = labels
.copy()
1041 # Returns the labels (not a copy) of the instance `instance_index`
1042 # of the group `group`.
1043 def labels(self
, group
: _Group
, instance_index
: int):
1044 return self
._instance
_labels
[group
][instance_index
]
1047 # Generator of data and labels from a group item.
1049 # Generation happens in memory at construction time. After building, use
1050 # the `data`, `variables`, `labels`, `offset`, and `bo` properties to
1051 # get the resulting context.
1059 bo
: Optional
[ByteOrder
],
1061 self
._group
_instance
_labels
= _GroupInstanceLabels()
1062 self
._resolve
_labels
(group
, offset
, labels
.copy())
1063 self
._vars
= variables
.copy()
1064 self
._offset
= offset
1066 self
._main
_group
= group
1074 # Updated variables.
1076 def variables(self
):
1079 # Updated main group labels.
1082 return self
._group
_instance
_labels
.labels(self
._main
_group
, 0)
1089 # Updated byte order.
1094 # Fills `self._group_instance_labels` with the labels for each group
1095 # instance in `item`, starting at current offset `offset` with the
1096 # current labels `labels`.
1098 # Returns the new current offset.
1099 def _resolve_labels(self
, item
: _Item
, offset
: int, labels
: VarsT
) -> int:
1100 if type(item
) is _Group
:
1101 # First pass: compute immediate labels of this instance
1102 group_labels
= labels
.copy()
1103 group_offset
= offset
1105 for subitem
in item
.items
:
1106 if type(subitem
) is _Offset
:
1107 group_offset
= subitem
.val
1108 elif type(subitem
) is _Label
:
1109 assert subitem
.name
not in group_labels
1110 group_labels
[subitem
.name
] = group_offset
1112 group_offset
+= subitem
.size
1114 # Add to group instance labels
1115 self
._group
_instance
_labels
.add(item
, group_labels
)
1117 # Second pass: handle each item
1118 for subitem
in item
.items
:
1119 offset
= self
._resolve
_labels
(subitem
, offset
, group_labels
)
1120 elif type(item
) is _Rep
:
1121 for _
in range(item
.mul
):
1122 offset
= self
._resolve
_labels
(item
.item
, offset
, labels
)
1123 elif type(item
) is _Offset
:
1130 def _handle_byte_item(self
, item
: _Byte
):
1131 self
._data
.append(item
.val
)
1132 self
._offset
+= item
.size
1134 def _handle_str_item(self
, item
: _Str
):
1135 self
._data
+= item
.data
1136 self
._offset
+= item
.size
1138 def _handle_bo_item(self
, item
: _Bo
):
1141 def _eval_expr(self
, item
: _ExprItemT
):
1142 # Get the labels of the current group instance as the initial
1143 # symbols (copied because we're adding stuff).
1144 assert self
._cur
_group
is not None
1145 syms
= self
._group
_instance
_labels
.labels(
1146 self
._cur
_group
, self
._group
_instance
_indexes
[self
._cur
_group
]
1149 # Set the `ICITTE` name to the current offset (before encoding)
1150 syms
[_icitte_name
] = self
._offset
1152 # Add the current variables
1153 syms
.update(self
._vars
)
1155 # Validate the node and its children
1156 _ExprValidator(item
, syms
).visit(item
.expr
)
1158 # Compile and evaluate expression node
1160 val
= eval(compile(item
.expr
, "", "eval"), None, syms
)
1161 except Exception as exc
:
1162 _raise_error_for_item(
1163 "Failed to evaluate expression `{}`: {}".format(item
.expr_str
, exc
),
1168 if type(val
) is not int:
1169 _raise_error_for_item(
1170 "Invalid expression `{}`: unexpected result type `{}`".format(
1171 item
.expr_str
, type(val
).__name
__
1178 def _handle_var_item(self
, item
: _Var
):
1180 self
._vars
[item
.name
] = self
._eval
_expr
(item
)
1182 def _handle_val_item(self
, item
: _Val
):
1184 val
= self
._eval
_expr
(item
)
1187 if val
< -(2 ** (item
.len - 1)) or val
> 2**item
.len - 1:
1188 _raise_error_for_item(
1189 "Value {:,} is outside the {}-bit range when evaluating expression `{}` at byte offset {:,}".format(
1190 val
, item
.len, item
.expr_str
, self
._offset
1195 # Encode result on 64 bits (to extend the sign bit whatever the
1196 # value of `item.len`).
1197 if self
._bo
is None and item
.len > 8:
1198 _raise_error_for_item(
1199 "Current byte order isn't defined at first value (`{}`) to encode on more than 8 bits".format(
1207 ">" if self
._bo
in (None, ByteOrder
.BE
) else "<",
1208 "Q" if val
>= 0 else "q",
1213 # Keep only the requested length
1214 len_bytes
= item
.len // 8
1216 if self
._bo
in (None, ByteOrder
.BE
):
1217 # Big endian: keep last bytes
1218 data
= data
[-len_bytes
:]
1220 # Little endian: keep first bytes
1221 assert self
._bo
== ByteOrder
.LE
1222 data
= data
[:len_bytes
]
1224 # Append to current bytes and update offset
1226 self
._offset
+= len(data
)
1228 def _handle_group_item(self
, item
: _Group
):
1229 # Update the instance index of `item`
1230 if item
not in self
._group
_instance
_indexes
:
1231 self
._group
_instance
_indexes
[item
] = 0
1233 self
._group
_instance
_indexes
[item
] += 1
1235 # Changed current group
1236 old_cur_group
= self
._cur
_group
1237 self
._cur
_group
= item
1240 for subitem
in item
.items
:
1241 self
._handle
_item
(subitem
)
1243 # Restore current group
1244 self
._cur
_group
= old_cur_group
1246 def _handle_rep_item(self
, item
: _Rep
):
1247 for _
in range(item
.mul
):
1248 self
._handle
_item
(item
.item
)
1250 def _handle_offset_item(self
, item
: _Offset
):
1251 self
._offset
= item
.val
1253 def _handle_item(self
, item
: _Item
):
1254 if type(item
) in self
._item
_handlers
:
1255 self
._item
_handlers
[type(item
)](item
)
1259 self
._data
= bytearray()
1260 self
._group
_instance
_indexes
= {} # type: Dict[_Group, int]
1261 self
._cur
_group
= None
1264 self
._item
_handlers
= {
1265 _Byte
: self
._handle
_byte
_item
,
1266 _Str
: self
._handle
_str
_item
,
1267 _Bo
: self
._handle
_bo
_item
,
1268 _Val
: self
._handle
_val
_item
,
1269 _Var
: self
._handle
_var
_item
,
1270 _Group
: self
._handle
_group
_item
,
1271 _Rep
: self
._handle
_rep
_item
,
1272 _Offset
: self
._handle
_offset
_item
,
1273 } # type: Dict[type, Callable[[Any], None]]
1275 # Handle the group item
1276 self
._handle
_item
(self
._main
_group
)
1279 # Returns a `ParseResult` instance containing the bytes encoded by the
1280 # input string `normand`.
1282 # `init_variables` is a dictionary of initial variable names (valid
1283 # Python names) to integral values. A variable name must not be the
1284 # reserved name `ICITTE`.
1286 # `init_labels` is a dictionary of initial label names (valid Python
1287 # names) to integral values. A label name must not be the reserved name
1290 # `init_offset` is the initial offset.
1292 # `init_byte_order` is the initial byte order.
1294 # Raises `ParseError` on any parsing error.
1297 init_variables
: Optional
[VarsT
] = None,
1298 init_labels
: Optional
[VarsT
] = None,
1299 init_offset
: int = 0,
1300 init_byte_order
: Optional
[ByteOrder
] = None,
1302 if init_variables
is None:
1305 if init_labels
is None:
1309 _Parser(normand
, init_variables
, init_labels
).res
,
1315 return ParseResult
._create
( # pyright: ignore[reportPrivateUsage]
1316 gen
.data
, gen
.variables
, gen
.labels
, gen
.offset
, gen
.bo
1320 # Parses the command-line arguments.
1321 def _parse_cli_args():
1325 ap
= argparse
.ArgumentParser()
1332 help="initial offset (positive)",
1338 choices
=["be", "le"],
1340 help="initial byte order (`be` or `le`)",
1346 help="add an initial variable (may be repeated)",
1353 help="add an initial label (may be repeated)",
1356 "--version", action
="version", version
="Normand {}".format(__version__
)
1363 help="input path (none means standard input)",
1367 return ap
.parse_args()
1370 # Raises a command-line error with the message `msg`.
1371 def _raise_cli_error(msg
: str) -> NoReturn
:
1372 raise RuntimeError("Command-line error: {}".format(msg
))
1375 # Returns a dictionary of string to integers from the list of strings
1376 # `args` containing `NAME=VAL` entries.
1377 def _dict_from_arg(args
: Optional
[List
[str]]):
1378 d
= {} # type: Dict[str, int]
1384 m
= re
.match(r
"({})=(\d+)$".format(_py_name_pat
.pattern
), arg
)
1387 _raise_cli_error("Invalid assignment {}".format(arg
))
1392 # CLI entry point without exception handling.
1397 args
= _parse_cli_args()
1400 if args
.path
is None:
1401 normand
= sys
.stdin
.read()
1403 with
open(args
.path
) as f
:
1406 # Variables and labels
1407 variables
= _dict_from_arg(args
.var
)
1408 labels
= _dict_from_arg(args
.label
)
1412 _raise_cli_error("Invalid negative offset {}")
1414 # Validate and set byte order
1415 bo
= None # type: Optional[ByteOrder]
1417 if args
.byte_order
is not None:
1418 if args
.byte_order
== "be":
1421 assert args
.byte_order
== "le"
1426 res
= parse(normand
, variables
, labels
, args
.offset
, bo
)
1427 except ParseError
as exc
:
1430 if args
.path
is not None:
1431 prefix
= "{}:".format(os
.path
.abspath(args
.path
))
1434 "{}{}:{} - {}".format(
1435 prefix
, exc
.text_loc
.line_no
, exc
.text_loc
.col_no
, str(exc
)
1440 sys
.stdout
.buffer.write(res
.data
)
1443 # Prints the exception message `msg` and exits with status 1.
1444 def _fail(msg
: str) -> NoReturn
:
1445 if not msg
.endswith("."):
1448 print(msg
, file=sys
.stderr
)
1456 except Exception as exc
:
1460 if __name__
== "__main__":