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 a repeatable item followed or not by a repetition,
867 # returning an item on success.
868 def _try_parse_item(self
):
869 self
._skip
_ws
_and
_comments
()
872 item
= self
._try
_parse
_base
_item
()
878 # Parse repetition if the base item is repeatable
879 if isinstance(item
, _RepableItem
):
880 rep
= self
._try
_parse
_rep
()
886 # Convert to repetition item
887 item
= _Rep(item
, rep
, self
._text
_loc
)
891 # Parses and returns items, skipping whitespaces, insignificant
892 # symbols, and comments when allowed, and stopping at the first
894 def _parse_items(self
) -> List
[_Item
]:
895 items
= [] # type: List[_Item]
897 while self
._isnt
_done
():
899 item
= self
._try
_parse
_item
()
906 # Unknown at this point
911 # Parses the whole Normand input, setting `self._res` to the main
912 # group item on success.
914 if len(self
._normand
.strip()) == 0:
915 # Special case to make sure there's something to consume
916 self
._res
= _Group([], self
._text
_loc
)
919 # Parse first level items
920 items
= self
._parse
_items
()
922 # Make sure there's nothing left
923 self
._skip
_ws
_and
_comments
()
925 if self
._isnt
_done
():
927 "Unexpected character `{}`".format(self
._normand
[self
._at
])
930 # Set main group item
931 self
._res
= _Group(items
, self
._text
_loc
)
934 # The return type of parse().
943 bo
: Optional
[ByteOrder
],
945 self
= cls
.__new
__(cls
)
946 self
._init
(data
, variables
, labels
, offset
, bo
)
949 def __init__(self
, *args
, **kwargs
): # type: ignore
950 raise NotImplementedError
958 bo
: Optional
[ByteOrder
],
961 self
._vars
= variables
962 self
._labels
= labels
963 self
._offset
= offset
971 # Dictionary of updated variable names to their last computed value.
976 # Dictionary of updated main group label names to their computed
987 # Updated byte order.
989 def byte_order(self
):
993 # Raises a parse error for the item `item`, creating it using the
995 def _raise_error_for_item(msg
: str, item
: _Item
) -> NoReturn
:
996 _raise_error(msg
, item
.text_loc
)
999 # The `ICITTE` reserved name.
1000 _icitte_name
= "ICITTE"
1003 # Value expression validator.
1004 class _ExprValidator(ast
.NodeVisitor
):
1005 def __init__(self
, item
: _ExprItemT
, syms
: VarsT
):
1008 self
._parent
_is
_call
= False
1010 def generic_visit(self
, node
: ast
.AST
):
1011 if type(node
) is ast
.Call
:
1012 self
._parent
_is
_call
= True
1013 elif type(node
) is ast
.Name
and not self
._parent
_is
_call
:
1014 # Make sure the name refers to a known label name
1015 if node
.id != _icitte_name
and node
.id not in self
._syms
:
1017 "Unknown variable/label name `{}` in expression `{}`".format(
1018 node
.id, self
._item
.expr_str
1020 self
._item
.text_loc
,
1023 # TODO: Restrict the set of allowed node types
1025 super().generic_visit(node
)
1026 self
._parent
_is
_call
= False
1029 # Keeper of labels for a given group instance.
1031 # A group instance is one iteration of a given group.
1032 class _GroupInstanceLabels
:
1034 self
._instance
_labels
= {} # type: Dict[_Group, Dict[int, VarsT]]
1036 # Assigns the labels `labels` to a new instance of `group`.
1037 def add(self
, group
: _Group
, labels
: VarsT
):
1038 if group
not in self
._instance
_labels
:
1039 self
._instance
_labels
[group
] = {}
1041 spec_instance_labels
= self
._instance
_labels
[group
]
1042 spec_instance_labels
[len(spec_instance_labels
)] = labels
.copy()
1044 # Returns the labels (not a copy) of the instance `instance_index`
1045 # of the group `group`.
1046 def labels(self
, group
: _Group
, instance_index
: int):
1047 return self
._instance
_labels
[group
][instance_index
]
1050 # Generator of data and labels from a group item.
1052 # Generation happens in memory at construction time. After building, use
1053 # the `data`, `variables`, `labels`, `offset`, and `bo` properties to
1054 # get the resulting context.
1062 bo
: Optional
[ByteOrder
],
1064 self
._group
_instance
_labels
= _GroupInstanceLabels()
1065 self
._resolve
_labels
(group
, offset
, labels
.copy())
1066 self
._vars
= variables
.copy()
1067 self
._offset
= offset
1069 self
._main
_group
= group
1077 # Updated variables.
1079 def variables(self
):
1082 # Updated main group labels.
1085 return self
._group
_instance
_labels
.labels(self
._main
_group
, 0)
1092 # Updated byte order.
1097 # Fills `self._group_instance_labels` with the labels for each group
1098 # instance in `item`, starting at current offset `offset` with the
1099 # current labels `labels`.
1101 # Returns the new current offset.
1102 def _resolve_labels(self
, item
: _Item
, offset
: int, labels
: VarsT
) -> int:
1103 if type(item
) is _Group
:
1104 # First pass: compute immediate labels of this instance
1105 group_labels
= labels
.copy()
1106 group_offset
= offset
1108 for subitem
in item
.items
:
1109 if type(subitem
) is _Offset
:
1110 group_offset
= subitem
.val
1111 elif type(subitem
) is _Label
:
1112 assert subitem
.name
not in group_labels
1113 group_labels
[subitem
.name
] = group_offset
1115 group_offset
+= subitem
.size
1117 # Add to group instance labels
1118 self
._group
_instance
_labels
.add(item
, group_labels
)
1120 # Second pass: handle each item
1121 for subitem
in item
.items
:
1122 offset
= self
._resolve
_labels
(subitem
, offset
, group_labels
)
1123 elif type(item
) is _Rep
:
1124 for _
in range(item
.mul
):
1125 offset
= self
._resolve
_labels
(item
.item
, offset
, labels
)
1126 elif type(item
) is _Offset
:
1133 def _handle_byte_item(self
, item
: _Byte
):
1134 self
._data
.append(item
.val
)
1135 self
._offset
+= item
.size
1137 def _handle_str_item(self
, item
: _Str
):
1138 self
._data
+= item
.data
1139 self
._offset
+= item
.size
1141 def _handle_bo_item(self
, item
: _Bo
):
1144 def _eval_expr(self
, item
: _ExprItemT
):
1145 # Get the labels of the current group instance as the initial
1146 # symbols (copied because we're adding stuff).
1147 assert self
._cur
_group
is not None
1148 syms
= self
._group
_instance
_labels
.labels(
1149 self
._cur
_group
, self
._group
_instance
_indexes
[self
._cur
_group
]
1152 # Set the `ICITTE` name to the current offset (before encoding)
1153 syms
[_icitte_name
] = self
._offset
1155 # Add the current variables
1156 syms
.update(self
._vars
)
1158 # Validate the node and its children
1159 _ExprValidator(item
, syms
).visit(item
.expr
)
1161 # Compile and evaluate expression node
1163 val
= eval(compile(item
.expr
, "", "eval"), None, syms
)
1164 except Exception as exc
:
1165 _raise_error_for_item(
1166 "Failed to evaluate expression `{}`: {}".format(item
.expr_str
, exc
),
1171 if type(val
) is not int:
1172 _raise_error_for_item(
1173 "Invalid expression `{}`: unexpected result type `{}`".format(
1174 item
.expr_str
, type(val
).__name
__
1181 def _handle_var_item(self
, item
: _Var
):
1183 self
._vars
[item
.name
] = self
._eval
_expr
(item
)
1185 def _handle_val_item(self
, item
: _Val
):
1187 val
= self
._eval
_expr
(item
)
1190 if val
< -(2 ** (item
.len - 1)) or val
> 2**item
.len - 1:
1191 _raise_error_for_item(
1192 "Value {:,} is outside the {}-bit range when evaluating expression `{}` at byte offset {:,}".format(
1193 val
, item
.len, item
.expr_str
, self
._offset
1198 # Encode result on 64 bits (to extend the sign bit whatever the
1199 # value of `item.len`).
1200 if self
._bo
is None and item
.len > 8:
1201 _raise_error_for_item(
1202 "Current byte order isn't defined at first value (`{}`) to encode on more than 8 bits".format(
1210 ">" if self
._bo
in (None, ByteOrder
.BE
) else "<",
1211 "Q" if val
>= 0 else "q",
1216 # Keep only the requested length
1217 len_bytes
= item
.len // 8
1219 if self
._bo
in (None, ByteOrder
.BE
):
1220 # Big endian: keep last bytes
1221 data
= data
[-len_bytes
:]
1223 # Little endian: keep first bytes
1224 assert self
._bo
== ByteOrder
.LE
1225 data
= data
[:len_bytes
]
1227 # Append to current bytes and update offset
1229 self
._offset
+= len(data
)
1231 def _handle_group_item(self
, item
: _Group
):
1232 # Update the instance index of `item`
1233 if item
not in self
._group
_instance
_indexes
:
1234 self
._group
_instance
_indexes
[item
] = 0
1236 self
._group
_instance
_indexes
[item
] += 1
1238 # Changed current group
1239 old_cur_group
= self
._cur
_group
1240 self
._cur
_group
= item
1243 for subitem
in item
.items
:
1244 self
._handle
_item
(subitem
)
1246 # Restore current group
1247 self
._cur
_group
= old_cur_group
1249 def _handle_rep_item(self
, item
: _Rep
):
1250 for _
in range(item
.mul
):
1251 self
._handle
_item
(item
.item
)
1253 def _handle_offset_item(self
, item
: _Offset
):
1254 self
._offset
= item
.val
1256 def _handle_item(self
, item
: _Item
):
1257 if type(item
) in self
._item
_handlers
:
1258 self
._item
_handlers
[type(item
)](item
)
1262 self
._data
= bytearray()
1263 self
._group
_instance
_indexes
= {} # type: Dict[_Group, int]
1264 self
._cur
_group
= None
1267 self
._item
_handlers
= {
1268 _Byte
: self
._handle
_byte
_item
,
1269 _Str
: self
._handle
_str
_item
,
1270 _Bo
: self
._handle
_bo
_item
,
1271 _Val
: self
._handle
_val
_item
,
1272 _Var
: self
._handle
_var
_item
,
1273 _Group
: self
._handle
_group
_item
,
1274 _Rep
: self
._handle
_rep
_item
,
1275 _Offset
: self
._handle
_offset
_item
,
1276 } # type: Dict[type, Callable[[Any], None]]
1278 # Handle the group item
1279 self
._handle
_item
(self
._main
_group
)
1282 # Returns a `ParseResult` instance containing the bytes encoded by the
1283 # input string `normand`.
1285 # `init_variables` is a dictionary of initial variable names (valid
1286 # Python names) to integral values. A variable name must not be the
1287 # reserved name `ICITTE`.
1289 # `init_labels` is a dictionary of initial label names (valid Python
1290 # names) to integral values. A label name must not be the reserved name
1293 # `init_offset` is the initial offset.
1295 # `init_byte_order` is the initial byte order.
1297 # Raises `ParseError` on any parsing error.
1300 init_variables
: Optional
[VarsT
] = None,
1301 init_labels
: Optional
[VarsT
] = None,
1302 init_offset
: int = 0,
1303 init_byte_order
: Optional
[ByteOrder
] = None,
1305 if init_variables
is None:
1308 if init_labels
is None:
1312 _Parser(normand
, init_variables
, init_labels
).res
,
1318 return ParseResult
._create
( # pyright: ignore[reportPrivateUsage]
1319 gen
.data
, gen
.variables
, gen
.labels
, gen
.offset
, gen
.bo
1323 # Parses the command-line arguments.
1324 def _parse_cli_args():
1328 ap
= argparse
.ArgumentParser()
1335 help="initial offset (positive)",
1341 choices
=["be", "le"],
1343 help="initial byte order (`be` or `le`)",
1349 help="add an initial variable (may be repeated)",
1356 help="add an initial label (may be repeated)",
1359 "--version", action
="version", version
="Normand {}".format(__version__
)
1366 help="input path (none means standard input)",
1370 return ap
.parse_args()
1373 # Raises a command-line error with the message `msg`.
1374 def _raise_cli_error(msg
: str) -> NoReturn
:
1375 raise RuntimeError("Command-line error: {}".format(msg
))
1378 # Returns a dictionary of string to integers from the list of strings
1379 # `args` containing `NAME=VAL` entries.
1380 def _dict_from_arg(args
: Optional
[List
[str]]):
1381 d
= {} # type: Dict[str, int]
1387 m
= re
.match(r
"({})=(\d+)$".format(_py_name_pat
.pattern
), arg
)
1390 _raise_cli_error("Invalid assignment {}".format(arg
))
1395 # CLI entry point without exception handling.
1400 args
= _parse_cli_args()
1403 if args
.path
is None:
1404 normand
= sys
.stdin
.read()
1406 with
open(args
.path
) as f
:
1409 # Variables and labels
1410 variables
= _dict_from_arg(args
.var
)
1411 labels
= _dict_from_arg(args
.label
)
1415 _raise_cli_error("Invalid negative offset {}")
1417 # Validate and set byte order
1418 bo
= None # type: Optional[ByteOrder]
1420 if args
.byte_order
is not None:
1421 if args
.byte_order
== "be":
1424 assert args
.byte_order
== "le"
1429 res
= parse(normand
, variables
, labels
, args
.offset
, bo
)
1430 except ParseError
as exc
:
1433 if args
.path
is not None:
1434 prefix
= "{}:".format(os
.path
.abspath(args
.path
))
1437 "{}{}:{} - {}".format(
1438 prefix
, exc
.text_loc
.line_no
, exc
.text_loc
.col_no
, str(exc
)
1443 sys
.stdout
.buffer.write(res
.data
)
1446 # Prints the exception message `msg` and exits with status 1.
1447 def _fail(msg
: str) -> NoReturn
:
1448 if not msg
.endswith("."):
1451 print(msg
, file=sys
.stderr
)
1459 except Exception as exc
:
1463 if __name__
== "__main__":