Skip to content

Commit 0df9e1e

Browse files
authored
Merge pull request #45 from hit9/dev-intx
[working in progress] Support signed integers with arbitrary bits (e.g. int24)
2 parents 9649715 + 824f4bb commit 0df9e1e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+1332
-348
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ dist
1212
.mypy_cache
1313
spam
1414
.DS_Store
15+
compile_flags.txt

changes.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
.. currentmodule:: bitproto
22

3+
.. _version-0.4.6:
4+
5+
Version 0.4.6
6+
-------------
7+
8+
- Support signed integers with arbitrary bits, e.g. int24 PR#45.
9+
310
.. _version-0.4.5:
411

512
Version 0.4.5

compiler/bitproto/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
99
"""
1010

11-
__version__ = "0.4.5"
11+
__version__ = "0.4.6"
1212
__description__ = "bit level data interchange format."

compiler/bitproto/_ast.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,20 @@
4242
"""
4343

4444
from collections import OrderedDict as dict_
45-
from dataclasses import dataclass
46-
from dataclasses import field as dataclass_field
47-
from typing import Any, Callable, ClassVar, Dict, List, Optional, Tuple
48-
from typing import Type as T
49-
from typing import TypeVar, Union, cast
45+
from dataclasses import dataclass, field as dataclass_field
46+
from typing import (
47+
Any,
48+
Callable,
49+
ClassVar,
50+
Dict,
51+
List,
52+
Optional,
53+
Tuple,
54+
Type as T,
55+
TypeVar,
56+
Union,
57+
cast,
58+
)
5059

5160
from bitproto.errors import (
5261
DuplicatedDefinition,
@@ -70,8 +79,8 @@
7079
PROTO_OPTTIONS,
7180
OptionDescriptor,
7281
OptionDescriptors,
82+
Validator as OptionValidator,
7383
)
74-
from bitproto.options import Validator as OptionValidator
7584
from bitproto.utils import (
7685
cache,
7786
conditional_cache,
@@ -706,21 +715,21 @@ def __repr__(self) -> str:
706715
@frozen
707716
@dataclass
708717
class Int(Integer):
709-
"""Int is the signed Integer.
710-
Different from uint, int type only supports capacity: 8, 16, 32, 64.
711-
"""
718+
"""Int is the signed Integer."""
712719

713720
cap: int = 0
714721

715-
@override(Node)
716-
def validate_post_freeze(self) -> None:
717-
if self.cap not in (8, 16, 32, 64):
718-
raise InvalidIntCap.from_token(token=self)
719-
720722
@override(Type)
721723
def nbits(self) -> int:
722724
return self.cap
723725

726+
@override(Node)
727+
def validate_post_freeze(self) -> None:
728+
if self._is_missing:
729+
return
730+
if not (0 < self.cap <= 64):
731+
raise InvalidIntCap.from_token(token=self)
732+
724733
def __repr__(self) -> str:
725734
return "<type int{0}>".format(self.cap)
726735

compiler/bitproto/errors.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616
"""
1717

1818
from dataclasses import dataclass
19-
from typing import TYPE_CHECKING, Any, ClassVar, Optional
20-
from typing import Type as T
21-
from typing import TypeVar
19+
from typing import TYPE_CHECKING, Any, ClassVar, Optional, Type as T, TypeVar
2220

2321
from bitproto.utils import Color, colored, overridable, override, write_stderr
2422

@@ -150,7 +148,7 @@ class InvalidUintCap(LexerError):
150148

151149
@dataclass
152150
class InvalidIntCap(LexerError):
153-
"""Invalid bits capacity for a int type, should be one of 8,16,32,64."""
151+
"""Invalid bits capacity for a int type, should between [1, 64]."""
154152

155153

156154
@dataclass

compiler/bitproto/lexer.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
"""
77

88
from contextlib import contextmanager
9-
from dataclasses import dataclass
109
from typing import Dict, Iterator, List, Optional, Tuple
1110

1211
from ply import lex # type: ignore
@@ -153,9 +152,6 @@ def t_UINT_TYPE(self, t: LexToken) -> LexToken:
153152

154153
def t_INT_TYPE(self, t: LexToken) -> LexToken:
155154
r"\bint[0-9]+\b"
156-
# Why use the regexp `int[0-9]+` instead of `int(8|16|32|64)`?
157-
# We want the unsupported cases like `int0`, `int3` to raise an error instead of
158-
# lexing into identifiers.
159155
cap: int = int(t.value[3:])
160156
t.value = Int(
161157
cap=cap, token=t.value, lineno=t.lineno, filepath=self.current_filepath()

compiler/bitproto/linter.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77

88
from abc import abstractmethod
99
from dataclasses import dataclass
10-
from typing import Callable, Generic, List, Optional, Tuple
11-
from typing import Type as T
12-
from typing import TypeVar
10+
from typing import Callable, Generic, List, Optional, Tuple, Type as T, TypeVar
1311

1412
from bitproto._ast import (
1513
Alias,

compiler/bitproto/parser.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,15 @@
77

88
import os
99
from contextlib import contextmanager
10-
from dataclasses import dataclass
11-
from dataclasses import field as dataclass_field
12-
from typing import Iterator, List, Optional, Tuple
13-
from typing import Type as T
14-
from typing import cast
10+
from typing import Iterator, List, Optional, Tuple, Type as T, cast
1511

1612
from ply import yacc # type: ignore
1713
from ply.lex import LexToken # type: ignore
18-
from ply.yacc import LRParser as PlyParser # type: ignore
19-
from ply.yacc import YaccProduction as P # type: ignore
14+
from ply.yacc import LRParser as PlyParser, YaccProduction as P # type: ignore
2015

2116
from bitproto._ast import (
2217
Alias,
2318
Array,
24-
BooleanConstant,
2519
Comment,
2620
Constant,
2721
Definition,
@@ -33,7 +27,6 @@
3327
Option,
3428
Proto,
3529
Scope,
36-
StringConstant,
3730
Type,
3831
)
3932
from bitproto.errors import (

compiler/bitproto/renderer/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from bitproto._ast import Proto
1111
from bitproto.errors import UnsupportedLanguageToRender
1212
from bitproto.renderer.impls import renderer_registry
13-
from bitproto.renderer.renderer import Renderer
1413

1514

1615
def render(

compiler/bitproto/renderer/block.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,22 @@
2222
from abc import abstractmethod
2323
from contextlib import contextmanager
2424
from dataclasses import dataclass
25-
from typing import Generic, Iterator, List, Optional
26-
from typing import Type as T
27-
from typing import TypeVar, Union, cast
25+
from typing import Generic, Iterator, List, Optional, Union
2826

2927
from bitproto._ast import (
3028
Alias,
3129
BoundDefinition,
3230
Comment,
3331
Constant,
3432
D,
35-
Definition,
3633
Enum,
3734
EnumField,
3835
Message,
3936
MessageField,
4037
Proto,
4138
)
4239
from bitproto.errors import InternalError
43-
from bitproto.renderer.formatter import F, Formatter
40+
from bitproto.renderer.formatter import F
4441
from bitproto.utils import (
4542
cached_property,
4643
final,
@@ -503,7 +500,7 @@ def render(self) -> None:
503500
self.after()
504501

505502
@abstractmethod
506-
def wraps(self) -> Block[F]:
503+
def wraps(self) -> Optional[Block[F]]:
507504
"""Returns the wrapped block instance."""
508505
raise NotImplementedError
509506

compiler/bitproto/renderer/formatter.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,18 @@
66
"""
77
import os
88
from abc import abstractmethod
9-
from enum import Enum as Enum_
10-
from enum import unique
11-
from typing import Callable, Dict, List, Optional, Tuple
12-
from typing import Type as T
13-
from typing import TypeVar, Union, cast
9+
from enum import Enum as Enum_, unique
10+
from typing import (
11+
Callable,
12+
Dict,
13+
List,
14+
Optional,
15+
Tuple,
16+
Type as T,
17+
TypeVar,
18+
Union,
19+
cast,
20+
)
1421

1522
from bitproto._ast import (
1623
Alias,
@@ -259,6 +266,21 @@ def definition_name_prefix_option_name(self) -> str:
259266
"""
260267
return ""
261268

269+
@overridable
270+
def post_format_op_mode_endecode_single_type(
271+
self, t: Type, chain: str, is_encode: bool
272+
) -> List[str]:
273+
"""
274+
Overridable hook function that would be called every time after a single type's encoding or decoding
275+
code is generated in the optimization mode. The lines returned by this function would be generated
276+
right follow the field' encoding (or decoding) code.
277+
278+
:param t: The type of this value.
279+
:param chain: The chained name in this message's encode/decode function.
280+
:param is_encode: Is generating code for encoding or decoding now.
281+
"""
282+
return []
283+
262284
###########
263285
# Finals
264286
###########
@@ -634,6 +656,9 @@ def format_op_mode_endecode_single_type(
634656
# Maintains the j and i counter
635657
j += c
636658
i[0] += c
659+
660+
# hook function
661+
l.extend(self.post_format_op_mode_endecode_single_type(t, chain, is_encode))
637662
return l
638663

639664
@final

compiler/bitproto/renderer/impls/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
Renderer implementations.
33
"""
44

5-
from typing import Dict, Tuple
6-
from typing import Type as T
5+
from typing import Dict, Tuple, Type as T
76

87
from bitproto.renderer.renderer import Renderer
98

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
from .renderer_c import RendererC
22
from .renderer_h import RendererCHeader
3+
4+
__all__ = ("RendererC", "RendererCHeader")

compiler/bitproto/renderer/impls/c/formatter.py

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
C formatter.
33
"""
44

5-
from typing import Optional
5+
from typing import List, Optional
66

77
from bitproto._ast import (
88
Alias,
@@ -17,7 +17,6 @@
1717
Message,
1818
MessageField,
1919
Proto,
20-
SingleType,
2120
Type,
2221
Uint,
2322
)
@@ -311,7 +310,7 @@ def format_op_mode_encoder_item(
311310
def format_op_mode_decoder_item(
312311
self, chain: str, t: Type, si: int, fi: int, shift: int, mask: int, r: int
313312
) -> str:
314-
"""Implements format_op_mode_encoder_item for C.
313+
"""Implements format_op_mode_decoder_item for C.
315314
Generated C statement like:
316315
317316
((unsigned char *)&((*m).color))[0] = (s[0] << 3) & 7;
@@ -320,3 +319,55 @@ def format_op_mode_decoder_item(
320319
assign = "=" if r == 0 else "|="
321320
shift_s = self.format_op_mode_smart_shift(shift)
322321
return f"((unsigned char *)&({chain}))[{fi}] {assign} (s[{si}] {shift_s}) & {mask};"
322+
323+
@override(Formatter)
324+
def post_format_op_mode_endecode_single_type(
325+
self, t: Type, chain: str, is_encode: bool
326+
) -> List[str]:
327+
"""
328+
Hook function called after a message field is generated.
329+
"""
330+
if isinstance(t, Alias):
331+
t = t.type
332+
if isinstance(t, Int):
333+
return self.post_format_op_mode_endecode_int(t, chain, is_encode)
334+
return []
335+
336+
def post_format_op_mode_endecode_int(
337+
self, t: Int, chain: str, is_encode: bool
338+
) -> List[str]:
339+
"""
340+
Process signed integers during decoding code generation in optimization mode.
341+
342+
Generated C statement example:
343+
344+
if (((*m).pressure_sensor.pressure >> 23) & 1) (*m).pressure_sensor.pressure |= -16777216;
345+
"""
346+
if is_encode:
347+
return []
348+
349+
# Signed integers processing is only about decoding
350+
n = t.nbits()
351+
352+
if n in {8, 16, 32, 64}:
353+
# No need to do additional actions
354+
# int8/16/32/64 signed integers' sign bit is already on the highest bit position.
355+
return []
356+
357+
m = ~((1 << n) - 1)
358+
mask = f"{m}"
359+
360+
if n == 63:
361+
# Avoid this compiler error for mask == -9223372036854775808;
362+
# warning: integer literal is too large to be represented in a signed integer type,
363+
# interpreting as unsigned [-Wimplicitly-unsigned-literal]
364+
mask = f"(-9223372036854775807 - 1)"
365+
366+
# For a signed integer e.g. 16777205, the most concise approach should be: 16777205 << 24 >> 24,
367+
# this shifts the 24th bit to the highest bit position, and then shift right again.
368+
# By doing an arithmetic right shifting, the leftmost sign bit got propagated, the result is -11.
369+
# This way is concise, but may not be portable.
370+
# Right shift behavior on negative signed integers is implementation-defined.
371+
# Another safe approach is: test the 24th bit at first ((16777205 >> 23) & 1), if it’s 1,
372+
# then do a OR operation with a mask, 16777205 |= ~(1<<24 -1) , the result is also -11.
373+
return [f"if (({chain} >> {n-1}) & 1) {chain} |= {mask};"]

0 commit comments

Comments
 (0)